why Axios promises doesn't get executed in order in React - reactjs

useEffect(() => {
if(reviewsData) {
reviewsData.forEach( item =>
{
if(item)
{
item.text.forEach((review,i) =>
{
axios({
method: "post",
url: "http://localhost:5000/retrieveuserbyid",
data: {
userId: item.user
},
headers: {
authToken: localStorage.getItem("authToken"),
"Content-Type": "application/json"
}
}).then(res => {
const name =res.data.name;
setTempUser([...tempUser , name]);
tempUser2 = [...tempUser2, name];
console.log(name);
console.log(tempUser2);
}).catch(err => {
});
}
)
}
}
);
}
}, [reviewsData]
);
reviewsData.text are comments people post on my site and I want to retriev username for each person leaving comments. (store them into a array initially) but I don't understand why in the state tempUser I have only the last user and in the tempUser2 I have all the users but in a random order each time I refresh. *One person can leave multiple comments.

React state updates are asynchronous. When you queue up all the axios promises the current value of tempUser is enclosed and used for all the state updates. If you use a functional state update, however, they can be correctly queued up and processed. Functional state updates allow each queued update to access the previous update, i.e. each updated depends on the previous one.
then(res => {
const name =res.data.name;
setTempUser(tempUser => ([...tempUser , name]));
tempUser2 = [...tempUser2, name];
console.log(name);
console.log(tempUser2);
})
Side note: Axios is capable of concurrent fetches (axios.all), and since axios uses promises, you can also map a bunch of requests and use Promise.all, which returns an array of all resolved axios requests, in the order they were queued up (if order matters).

Related

How can I optimize my code to stop sending GET requests constantly?

I am using the Yelp Fusion API to get a list of restaurants from Yelp. However, I am always constantly sending a GET request and I am not sure what is going on or how to fix it. I have tried React.memo and useCallback. I think the problem lies within how I am making the call rather than my component rerendering.
Here is where I send a GET request
// Function for accessing Yelp Fusion API
const yelpFusionSearch = async () => {
try {
const response = await yelp.get('/businesses/search', {
params: {
term: food,
location: location
}
})
// Saving our results, getting first 5 restaurants,
// and turning off our loading screen
setYelpResults({businesses: response.data.businesses.splice(0, 5)});
setEnableLoading(1);
}
catch (error) {
setEnableLoading(2);
}
};
This is where I use axios.
// Our Yelp Fusion code that sends a GET request
export default axios.create({
baseURL: `${'https://cors-anywhere.herokuapp.com/'}https://api.yelp.com/v3`,
headers: {
Authorization: `Bearer ${KEY}`
},
})
You are probably calling that function within your functional component and that function sets a state of that component, so it re-renders. Then the function is executed again, sets state, re-renders and so on...
What you need to do is to wrap that API call inside a:
useEffect(() => {}, [])
Since you probably want to call it one time. See useEffect doc here
You can do 2 things either use a button to get the list of restaurants because you are firing your function again and again.
const yelpFusionSearch = async () => {
try {
const response = await yelp.get('/businesses/search', {
params: {
term: food,
location: location
}
})
Use a button instead maybe so once that button is clicked function is fired.
<button onClick={yelpFusionSearch} />Load More Restaurants </button>
Use your fuction inside useEffect method which will load 5 restaurants once the page renders
useEffect(() => {
const yelpFusionSearch = async () => {
try {
const response = await yelp.get('/businesses/search', {
params: {
term: food,
location: location
}
})
}, [])

fetching data in React useEffect() after adding item to database using graphql and axios crashes the app

Initially the initialFetch is true, so whenever the component renders graphql and axios fetch data from db. Then initialFetch is set to false.
Once an event is added to db via graphql and axios there added state variable is set to true. Since useEffect depends on added it should re-render the component and should fetch the data from db. But for some reason it fails as I mentioned below axios fails at communicating with the server.
Note! I Used GraphQL for fetching data from MongoDB
const [added, setAdded] = useState(false)
const [initialFetch, setInitialFetch] = useState(true)
useEffect(() => {
const fetchEvents = () => {
console.log('inside fetchEvents()')
const headers = {
'Content-Type': 'application/json'
}
const requestBody = {
query: `
query {
events {
_id
title
description
price
}
}
`
}
const body = JSON.stringify(requestBody)
console.log('awaiting for events from db')
axios.post('http://localhost:5000/graphql', body, {headers}).then((res) => {
console.log('events fetched from db')
setEvents(res.data.data.events)
}).catch((err) => {
console.log(err)
})
}
if (initialFetch) {
setInitialFetch(false)
console.log('initial fetch')
fetchEvents()
}
if (added) {
setAdded(false)
console.log('added, fetching again')
fetchEvents()
}
}, [added, initialFetch])
Here axios fails to add data to db and catch(err) block is executed after waiting over 2 minutes and the app crashes. The below code where axios posts data continuously keeps failing every time I try.
const handleConfirm = () => {
// request to backend
const headers = {
'Content-Type': 'application/json',
'Authorization': `Bearer ${authContext.token}`
}
const requestBody = {
query: `
mutation {
createEvent(title: "${title}", description: "${desc}", price: ${price}, date: "${date}") {
_id
title
description
price
}
}
`
}
const body = JSON.stringify(requestBody)
console.log('adding to db')
axios.post('http://localhost:5000/graphql', body, {headers}).then((res) => {
setAdded(true)
console.log('added item to db')
console.log(res.data)
}).catch((err) => {
console.log(err)
})
}
the initial fetch happens and I add data to db. After adding to db I should be re-fetched the events from db, instead that re-render fails and the app crashes.
That error is a classic sign that you have an infinite loop in your code. It is particularly common when that infinite loop is due to infinite recursion. Each time you call a function, JS has to allocate some stack space for the stackframe. If that function always calls itself, then it will keep trying to allocate more and more space until eventually, it crashes due to having no more memory available to allocate from.
Try removing the unguarded call to fetchEvents() in your useEffect() code block.
Yes, the app will definitely crash as it is updating the state recursively and indefinitely.
as every time the value of added is updated the useEffect is fired and as the useEffect fires it triggers axios.post which inturn again updates the state added

When to use Redux to fetch data from api call

I adopted Redux in my project for state control, and also Axios for fetching api in action.
But I wonder when should I fetch data using API call through Redux (in action), when should I directly make the api call in component.
Is it depending on, whether I need to store the response data in Redux (for sharing among different components)? May I know any best practice for it?
API call through Redux
export const fetchOptions = () => {
return async (dispatch, getState) => {
const request = await client.query({
query: gqlQueries.getQuery,
});
const options = await request;
dispatch({
type: types.FETCH_DATA_END,
options: options
});
}
}
Directly make API call in component:
const axios = require("axios");
useEffect(() => {
axios({
url: 'http://localhost/graphql',
method: 'post',
data: {
query: `
query PostsForAuthor {
author(id: 1) {
firstName
posts {
title
votes
}
}
}
`
}
}).then((result) => {
console.log(result.data)
});
}, []);
If multiple components are using the same data, redux shines there. API calls in components are preferred when you do not want any stale data to show, therefore you call api every time component mounts and your data is always in sync with your back end. There might be some other criteria but these two help me decide , where to keep the state.

whats the proper way to make dependent api call in react redux

In my application, we maintain an inventory of authors and books and the vendors who are selling those books.
In my react js app I have a home page which displays a table with a list of authors and when we click on any author we go to author details page and URL will be like ${url}/author/12 . This author details page consists of three sections .First div contains author details like name, city, country.Second div contains list of books author has published so far which is a table and by default first record is selected.Third div contains the details of each book like title, description, price when a book is selected from second div .
In this author details page i use the id from props.match.params.id and make first api call to fetch author details, if success then fetch books if that is success get selected book details. I am confused between two approaches i have.
Method 1:
Make all the api calls from ui component based on data recieved. Make use of componentwillReceiveProps to know when the author details are fetched or books are fetched. Is componentwillReceiveProps the right place to decide to make subsequent api calls ?
class AuthorPage extends React.Component {
state = {
authorId:this.props.match.params.id,
}
componentDidMount() {
this.props(fetchAuthorDetails(authorId));
}
componentWillReceiveProps(newProps){
//Is this the right place to decide the subsequent api calls ?
if(isNotEmpty(newProps.author.details))
{
this.props(fetchAuthorBooks(authorId));
}
else if (newProps.author.books.length > 0 && isNotEmpty(newProps.author.selectedBook)){
this.props(fetchSelectedBookDetails(newProps.author.selectedBook.id));
}
}
render() {
// UI render code goess here
}
}
const mapDispatchToProps = dispatch => bindActionCreators(
{
fetchAuthorDetails,
fetchAuthorBooks,
fetchSelectedBookDetails
},
dispatch,
)
const mapStateToProps = (state, ownProps) => ({
authorDetails: state.author.details,
books: state.author.books,
selectedBook: state.author.selectedBook,
selectedBookDetails : state.author.selectedBook.details
});
export default connect(mapStateToProps)(AuthorPage);
Method2:
Is it better to make Subsequent dispatch calls (from ActionCreators dispatch calls depending on success/fail) like I am calling authorDetailsSuccess /BooksActionCreators.getAuthorBookDetails and authorDetailsError/ErrorsActionsCreator in the below code? And should i have separate action creators file for authors and books.
export function fetchAuthorDetails(authorId) {
dispatch(authorDetailsStart());
return dispatch =>
fetch('http://localhost/api', {
method: 'get',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: userData.email,
password: userData.password,
}),
})
.then(response => {
if (response.status >= 200 && response.status < 300) {
console.log(response);
dispatch(authorDetailsSuccess(authorDetails));
dispatch(BooksActionCrerators.getAuthorBookDetails(authorDetails.id)); //is this the right place to chain
} else {
const error = new Error(response.statusText);
error.response = response;
dispatch(authorDetailsError());
dispatch(ErrorsActionsCreator.sendError(error));
throw error;
}
})
.catch(error => { console.log('request failed', error); });
}
Personally I always add redux-thunk module to my projects. This module transform a simple redux action to a promise-like function. So I can concatenate multiple actions by using classic .then() sintax. Check documentation for more details.

How to handle the case where the axios response comes after the component is rendered?

I am creating a blog application in rest framework and reactjs. On the home page, under componentDidMount, I send an API call using axios to get all the articles and setState of articles to the return. As I have studied, axios works on the idea of promise such that the code doesnt proceed, if the API is not fetched for a particular component. Please tell me, if I am wrong.
Then, I send a GET call to get the writer's name, who wrote the article by the id. Though, I assumed that the axios works as a promise. But, it doesnt work that way. Now, I am not sure how to move ahead.
Here is a snippet. So, in mainBody.js, I make the api call as:
class MainBody extends Component {
state = {};
componentDidMount () {
this.get_all_articles();
};
get_writer_name (id) {
let authstr = 'Bearer ' + window.localStorage.token;
let writer_url = "http://localhost:8000/api/writer/" + id.toString() + "/";
axios.get(writer_url, { headers: { Authorization: authstr }})
.then(response => {
console.log(response.data['name'])
return response.data['name'];
})
.catch(err => {
console.log("Got error")
})
};
get_all_articles () {
let authstr = 'Bearer ' + window.localStorage.token;
axios.get("http://localhost:8000/api/articles/", { headers: { Authorization: authstr }})
.then(response => {
this.setState({articles: response.data});
})
.catch(err => {
console.log("Got error")
})
}
render () {
return (
{this.state.articles.map((article, key) =>
<ArticleView key={article.id} article={article} writer_name={this.get_writer_name(article.created_by)} />
)}
)
}
}
In articleview2, I print all the data that is present in each of the articles along with the writer's name.
My articleview class is:
class ArticleView extends Component {
state = {article: this.props.article};
componentDidMount() {
console.log(this.props.writer_name;
}
render () {
return (
<React.Fragment>
<h2>{article.title}</h2>
<p>{article.body}</p>
<span>{this.props.writer_name}</span>
</React.Fragment>
)
}
}
If you see closely, I wrote two console.log statements to get the writer names. Based on the order, first the console log present in articleview class runs, which is undefined, and thenafter the data is fetched from the API call and the console log runs which returns the correct writer name.
I wanted to know, where is the error? Also, as I noticed, there are too many API calls being made to get the writer's name multiple time for all the listed articles. What are the industry best practices for these cases?
I want to know where is the error.
When you are writing this.state.articles.map(), means you're using property map of the Array articles which may be undefined before the data is fetched that will cause you the error Cannot read property map of undefined.
Solution
Now, as the API request is asynchronous, means render method will not wait for the data to come. So what you can do is use a loader variable in the state, and set it to true as long as the request is being made, and when the response has come, make it false, and show the loader in render when this.state.loader is true, and show articles when it is false.
Or you can initialize this.state.articles with an empty array that won't cause you the error.
Also, as I noticed, there are too many API calls being made to get the writer's name multiple time for all the listed articles. What are the industry best practices for these cases?
It is extremely bad practice to make an API request in the loop. Even myself has been scolded on it once I did it in my company.
Solution
You have tell your backend engineer to provide you filter for including the writer's name in each object of the article. We use Loopback on our backend, which provides a filter for including the related model in each object internally.
Since your API calls have a lot of things in common, you should first set up an axios instance that re-uses those common features:
const api = axios.create({
baseURL: 'http://localhost:8000/api/',
headers: { Authorization: `Bearer ${localStorage.token}` }
});
Now since your MainBody needs to fetch the resources from the API asynchronously, there will be a short period where the data is not yet available. There are two ways you can handle this. Either the MainBody can be responsible for making all the calls, or it can be responsible for just making the call to get all the articles, then each of the ArticleView components can be responsible for getting the writer's name. I'll demonstrate the first approach below:
class MainBody extends Component {
state = { articles: null, error: null, isLoading: true };
async componentDidMount () {
try {
const response = await api.get('articles/');
const articles = await Promise.all(
response.data.map(async article => {
const response = await api.get(`writer/${article.created_by}/`);
return { ...article, writer_name: response.data.name };
})
);
this.setState({ articles, isLoading: false });
} catch (error) {
this.setState({ error, isLoading: false });
}
}
render () {
const { articles, error, isLoading } = this.state;
return isLoading ? 'Loading...' : error
? `Error ${error.message}`
: articles.map(article => (
<ArticleView
key={article.id}
article={article}
writer_name={article.writer_name}
/>
)
);
}
}

Resources