Test a function that calls an API - javascript

I need to test the fetchData() function. I have been trying to follow this (and many other) tutorials and trying to get it to work with my function for the past 3 hours but no luck so far. I'd preferably want to do it another way anyway because I don't think jQuery and React should be used together.
I essentially want to check if 1) the function is called when the search form has been submitted (button within SearchForm clicked) and 2) check if the data comes back.
Does anyone have any suggestions on how to get started please?
Thanks
Home
export default class Home extends Component {
constructor() {
super();
this.state = {
value: '',
loading: false,
dataError: false
}
this.nodes = [];
this.fetchData = this.fetchData.bind(this);
}
fetchData(e) {
e.preventDefault();
this.setState({loading: true});
axios.get(`https://api.github.com/search/repositories?q=${this.state.value}`)
.then(res => {
this.nodes = res.data.items.map((d, k) => <RepoItem {...d} key={k}/>);
this.setState({loading: false});
})
.catch(err => {
this.setState({dataError: true});
});
}
render() {
return (
<div className="home-wrapper">
<SearchForm value={this.state.value}
onSubmit={this.fetchData}
onChange={(e) => this.setState({value: e.target.value})}/>
{this.state.loading ?
<Spinner/>:
!this.state.dataError ? this.nodes :
<h1>Oops! Something went wrong, please try again!</h1>}
</div>
);
}
}
RepoItem
export const RepoItem = props => (
<div className="repo-item">
<h1>{props.full_name}</h1>
</div>);

To check if the function is called upon form submission, you can shallow-render the <SearchForm> component with the spy function passed as a onSubmit prop. Simulate the submit event and check if the spy function is called.
To check if the data comes back, mock the axios service. You can use this library to mock axios calls. Call the fetchData() and see if the this.nodes and state updated correctly.
const wrapper = shallow(<Home />);
wrapper.instance().fetchData().then(() => {
... your assertions go here ...
})
I think it's always the best practice to return a Promise object or any chainable object from a method where asynchronous action takes place.

Related

React - change this.state onClick rendered with array.map()

I'm new to React and JavaScript.
I have a Menu component which renders an animation onClick and then redirects the app to another route, /coffee.
I would like to pass the value which was clicked (selected) to function this.gotoCoffee and update this.state.select, but I don't know how, since I am mapping all items in this.state.coffees in the same onClick event.
How do I do this and update this.state.select to the clicked value?
My code:
class Menus extends Component{
constructor (props) {
super(props);
this.state = {
coffees:[],
select: '',
isLoading: false,
redirect: false
};
};
gotoCoffee = () => {
this.setState({isLoading:true})
setTimeout(()=>{
this.setState({isLoading:false,redirect:true})
},5000)
}
renderCoffee = () => {
if (this.state.redirect) {
return (<Redirect to={`/coffee/${this.state.select}`} />)
}
}
render(){
const data = this.state.coffees;
return (
<div>
<h1 className="title is-1"><font color="#C86428">Menu</font></h1>
<hr/><br/>
{data.map(c =>
<span key={c}>
<div>
{this.state.isLoading && <Brewing />}
{this.renderCoffee()}
<div onClick={() => this.gotoCoffee()}
<strong><font color="#C86428">{c}</font></strong></div>
</div>
</span>)
}
</div>
);
}
}
export default withRouter(Menus);
I have tried passing the value like so:
gotoCoffee = (e) => {
this.setState({isLoading:true,select:e})
setTimeout(()=>{
this.setState({isLoading:false,redirect:true})
},5000)
console.log(this.state.select)
}
an like so:
<div onClick={(c) => this.gotoCoffee(c)}
or so:
<div onClick={(event => this.gotoCoffee(event.target.value}
but console.log(this.state.select) shows me 'undefined' for both tries.
It appears that I'm passing the Class with 'c'.
browser shows me precisely that on the uri at redirect:
http://localhost/coffee/[object%20Object]
Now if I pass mapped 'c' to {this.renderCoffee(c)}, which not an onClick event, I manage to pass the array items.
But I need to pass not the object, but the clicked value 'c' to this.gotoCoffee(c), and THEN update this.state.select.
How do I fix this?
You can pass index of element to gotoCoffee with closure in render. Then in gotoCoffee, just access that element as this.state.coffees[index].
gotoCoffee = (index) => {
this.setState({isLoading:true, select: this.state.coffees[index]})
setTimeout(()=>{
this.setState({isLoading:false,redirect:true})
},5000)
}
render(){
const data = this.state.coffees;
return (
<div>
<h1 className="title is-1"><font color="#C86428">Menu</font></h1>
<hr/><br/>
{data.map((c, index) =>
<span key={c}>
<div>
{this.state.isLoading && <Brewing />}
{this.renderCoffee()}
<div onClick={() => this.gotoCoffee(index)}
<strong><font color="#C86428">{c}</font></strong></div>
</div>
</span>)
}
</div>
);
}
}
so based off your code you could do it a couple of ways.
onClick=(event) => this.gotoCoffee(event.target.value)
This looks like the approach you want.
onClick=() => this.gotoCoffee(c)
c would be related to your item in the array.
All the answers look alright and working for you and it's obvious you made a mistake by not passing the correct value in click handler. But since you're new in this era I thought it's better to change your implementation this way:
It's not necessary use constructor at all and you can declare a state property with initial values:
class Menus extends Component{
state= {
/* state properties */
};
}
When you declare functions in render method it always creates a new one each rendering which has some cost and is not optimized. It's better if you use currying:
handleClick = selected => () => { /* handle click */ }
render () {
// ...
coffees.map( coffee =>
// ...
<div onClick={ this.handleClick(coffee) }>
// ...
}
You can redirect with history.replace since you wrapped your component with withRouterand that's helpful here cause you redirecting on click and get rid of renderCoffee method:
handleClick = selected => () =>
this.setState(
{ isLoading: true},
() => setTimeout(
() => {
const { history } = this.props;
this.setState({ isLoading: false });
history.replace(`/${coffee}`);
}
, 5000)
);
Since Redirect replaces route and I think you want normal page change not replacing I suggest using history.push instead.
You've actually almost got it in your question. I'm betting the reason your state is undefined is due to the short lived nature of event. setState is an asynchronous action and does not always occur immediately. By passing the event off directly and allowing the function to proceed as normal, the event is released before state can be set. My advice would be to update your gotoCoffee function to this:
gotoCoffee = (e) => {
const selectedCoffee = e.target.value
this.setState({isLoading:true,select:selectedCoffee},() =>
{console.log(this.state.select})
setTimeout(()=>{
this.setState({isLoading:false,redirect:true})
},5000)
}
Note that I moved your console.log line to a callback function within setState so that it's not triggered until AFTER state has updated. Any time you are using a class component and need to do something immediately after updating state, use the callback function.

Dispatch to props only when clicked

The issue I am having is that the mapDispatchToProps is getting sent as a whole, where I want it to only send if I click on the button delete.
This is my class, its fetching the fetchList good, everything is working as expected but when I've added the delete button its seems to mess it all up, it seems to call the delete for every refresh on the page, any idea why?
Could it be the render() where I create the Button maybe it gets triggered without me clicking it? Just by creating the list, because it gets triggered for every occasion that each itemInList gets created via the map.
class List extends Component {
componentWillMount() {
this.props.fetchList();
}
componentWillReceiveProps(nextProps) {
if (nextProps.newItem) {
this.props.list.unshift(nextProps.newItem);
}
}
onDelete = (id) => {
this.props.deleteItem(id);
}
render() {
const listItems = this.props.list.map(itemInList => (
<div key={itemInList.id}>
<h3 className="title__font smaller">{itemInList.title}
<Button
btnType="Delete"
onClick={this.onDelete(itemInList.id)}>
<i className="fas fa-trash-alt"></i>
</Button>
</h3>
<p className="body__font">{itemInList.body}</p>
</div>
));
return (
<div>
<h1 className="title__font">List</h1>
{ listItems }
</div>
);
};
};
List.propTypes = {
fetchList: PropTypes.func.isRequired,
list: PropTypes.array.isRequired,
newItem: PropTypes.object
};
const mapStateToProps = state => ({
list: state.list.items,
newItem: state.list.item
});
const mapDispatchToProps = dispatch => {
return {
fetchList: () => dispatch( actions.fetchList() ),
deleteItem: (id) => dispatch( actions.deleteItem(id) )
};
};
export default connect(mapStateToProps, mapDispatchToProps)(List);
This is the actions for the delete item:
export const deleteItem = (id) => dispatch => {
console.log(id);
dispatch({
type: actionTypes.DELETE_ITEM,
payload: filtered
})
};
That log gets triggered 10 times in the actions file.
You're going to want to pass a function declaration into onClick and you'll need to pass the id in somehow. We don't want to declare any functions in the render method for performance issues, but we need some way to pass the id into the method upon invocation. Data attributes are a great solution to this problem.
Here is some relevant documentation.
React: Handling Events
HTMLElement.dataset
babel-plugin-proposal-class-properties
First, ensure the this context of your method is bound to the component as follows:
constructor(props) {
super(prop)
this.onDelete = this.onDelete.bind(this)
}
The above is required because class methods are actually defined on their prototype and not on individual instantiations. Side note: If you're using a build system that has something along the lines of babel-plugin-proposal-class-properties you could declare your method as follows:
onDelete = (e) => { this.props.deleteItem(e.target.dataset.id) }
You'll need to update your onDelete method as follows:
onDelete = (e) => {
this.props.deleteItem(e.target.dataset.id);
}
and you'll also need to update the Button markup in your render method like this:
<Button
btnType="Delete"
data-id={itemInList.id}
onClick={this.onDelete}
>
<i className="fas fa-trash-alt"></i>
</Button>
EDIT:
Here is a working Code Sandbox to demonstrate how it all works. I had to make some changes to your code such as excluding the non-included <Button/> component. I hope this helps you get where you need to be.
You're calling onDelete immediately, so it will dispatch on render.
Try replacing:
onDelete = (id) => {
this.props.deleteItem(id);
}
with
onDelete = (id) => () => this.props.deleteItem(id);

Creating a React higher order component, to serve as a "loader"(animation) wrapper for child components

I have a lot of components, that require some ajax function being sent, in the componentDidMount method. I would like to create a HOC, whose sole purpose is to "apply" some animation to the component, and stop this animation once a certain promise is resolved.
Of course, i could just copy paste this code for each component, but i would like to create some abstraction that deals with it.
The problem is, that i don't know how to pass the function properly, from the child to the parent. For instance, let's assume the intended child component, has this componentDidMount:
componentDidMount() {
ajax('/costumers')
.then(({ data }) => {
this.setState(() => ({ costumers: data.content }))
})
}
Technically, i need to either pass this function as an argument to the HOC, or perhaps somehow "hijack" the child's componentDidMount(if something like that is possible...). The HOC would then apply an animation once it's loaded, then send the ajax, and only when it's solved, the animation is eliminated, and the child component gets rendered.
How can this be achieved?
Any idea will be appreciated
Here is how you can write a HOC for such a case, refer to React docs for more info on the subject.
const withLoader = (loader, Component) =>
class WithLoader extends React.Component {
state = { ready: false, data: null };
async componentDidMount() {
const data = await loader();
this.setState({ ready: true, data });
}
render() {
if (!this.state.ready) return <div>LOADING</div>; // or <ComponentWithAnimation />
return <Component data={this.state.data} />;
}
};
const Test = props => <div>DATA: {props.data}</div>;
const fakeLoader = () =>
new Promise(res => setTimeout(() => res("My data"), 1000));
const TestWithLoader = withLoader(fakeLoader, Test);

How do I create an array after Axios response and Display it in React js

I am having trouble creating an array of titles from an Axios response. The method getTitles(props) receives data from the Axios response. How do I create an array of titles dynamically?
The functions I have tried in Javascript are for loops and EC6 mapping, nothing seems to work. Being new to react I could be missing something but I am not sure what it is.
React code
export default class Featured extends React.Component {
constructor() {
super();
this.state = {
data: null,
}
}
/**
* Received request from server
*/
componentDidMount(){
ApiCalls.articleData()
.then(function(data){
this.setState(function(){
return {
data: data
}
})
}.bind(this));
}
getTitles(props){
//-- What code do I place here?
console.log(props.data)
return ['test title', 'test title 2'];
}
/**
* Render request
*/
render() {
let dataResponse = JSON.stringify(this.state.data, null, 2);
const Articles = this.getTitles(this.state).map((title, i) => <Article key={i} title={title}/> );
return (
<div class="row">{Articles}
<pre>{dataResponse}</pre>
</div>
);
}
}
Axios Code
var ApiCalls = {
articleData: function(id){
return axios.all([getArticles(id)])
.then(function(arr){
return arr[0].data.data;
})
.catch(function (error) {
console.log(error);
})
},
React setState behaves asynchronously . Your articles get rendered before the ajax was called and was not re rendered due to asynchrony in setState.
This is what doc(https://reactjs.org/docs/react-component.html#setstate) says
setState() does not always immediately update the component. It may batch or defer the update until later. This makes reading this.state right after calling setState() a potential pitfall. Instead, use componentDidUpdate or a setState callback (setState(updater, callback)), either of which are guaranteed to fire after the update has been applied. If you need to set the state based on the previous state, read about the updater argument below.
You can render the article after successful ajax call like below
componentDidMount(){
ApiCalls.articleData()
.then(function(data){
render(<Article data={data}/>, document.getElementById('...'));
}.bind(this));
}
Because of the post above, I was able to fix the issue by the code example below To see a working example goto the git repo
ApiCalls.articleData()
.then(function(data){
const newData = data.map(c => {
return c.attributes.title;
})
const addElement = newData.map((title, i) => <ContentTiles key={i} title={title}/> );
const newState = Object.assign({}, this.state, {
newData: addElement
});
this.setState(newState);
}.bind(this));

How store data from fetch

I'm pretty new in React and need some help.
I wanted display data from a movie database based on the search term. I'm using fetch inside my getMovies methode to get the data. The data is stored in data.Search but I don't know how to access it and store it in a variable.
class DataService
{
getMovies (searchTerm) {
//let dataStorage; //store somehow data.Search inside
fetch("http://www.omdbapi.com/?s=" + searchTerm, {
method: 'get'
})
.then((resp) => resp.json())
.then(function(data) {
return data.Search;
}).catch(function(error) {
console.log(error);// Error :(
});
}
//return dataStorage; //return data.Search
}
The below code is the correct react's way for your case, as simple as this:
import React from 'react';
export default class DataService extends React.Component {
constructor(props) {
super(props);
this.state = {
search_data: [], //for storing videos
};
this.getMovies = this.getMovies.bind(this); //bind this function to the current React Component
}
getMovies (searchTerm) {
fetch("http://www.omdbapi.com/?s=" + searchTerm, {
method: 'get'
})
.then((resp) => resp.json())
.then((data) => { //so that this callback function is bound to this React Component by itself
// Set state to bind search results into "search_data"
// or you can set "dataStorage = data.Search" here
// however, fetch is asynchronous, so using state is the correct way, it will update your view automatically if you render like I do below (in the render method)
this.setState({
search_data: data.Search,
});
});
}
componentDidMount() {
this.getMovies(); //start the fetch function here after elements appeared on page already
}
render() {
return (
{this.state.search_data.map((video, i) =>{
console.log(video);
// please use the correct key for your video below, I just assume it has a title
return(
<div>{video.title}</div>
)
})}
);
}
}
Feel free to post here any errors you may have, thanks
There are several ways of doing asynchronous tasks with React. The most basic one is to use setState and launch a Promise in the event handler. This might be viable for basic tasks but later on, you will encounter race conditions and other nasty stuff.
In order to do so, your service should return a Promise of results. On the React side when the query changes, the service is called to fetch new results. While doing so, there are few state transitions: setting loading flag in order to notify the user that the task is pending and when the promise resolves or rejects the data or an error is stored in the component. All you need is setState method.
More advanced techniques are based on Redux with redux-thunk or redux-saga middlewares. You may also consider RxJS - it is created especially for that kind of stuff providing debouncing, cancellation and other features out of the box.
Please see the following example of simple search view using yours DataService.
class DataService
{
//returns a Promise of search results, let the consumer handle errors
getMovies (searchTerm) {
return fetch("http://www.omdbapi.com/?s=" + searchTerm, {
method: 'get'
})
.then((resp) => resp.json())
.then(function(data) {
return data.Search;
})
}
}
const SearchResults = ({data, loading, error}) =>
<div>
{
data && data.map(({Title, Year, imdbID, Type, Poster}) =>
<div key={imdbID}>
{Title} - {Year} - {Type}
<img src={Poster} />
</div>
)
}
{loading && <div>Loading...</div>}
{error && <div>Error {error.message}</div>}
</div>
class SearchExample extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this)
this.state = {
data: [],
loading: false
};
}
handleChange(event) {
const service = new DataService();
//search query is target's value
const promise = service.getMovies(event.target.value);
//show that search is being performed
this.setState({
loading: true
})
//after the promise is resolved display the data or the error
promise.then(results => {
this.setState({
loading: false,
data: results
})
})
.catch(error => {
this.setState({
loading: false,
error: error
})
})
}
render() {
return (
<div>
<input placeholder="Search..." onChange={this.handleChange} type="search" />
<SearchResults data={this.state.data} loading={this.state.loading} error={this.state.error} />
</div>
)
}
}
ReactDOM.render(<SearchExample />, document.getElementById("app"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>

Categories

Resources