Component Renders too early - reactjs

I have a class that does a lot of API Calls to populate the state so I'm doing it in the componentDidMount() lifecycle hook like this:
async componentDidMount() {
await this.populateQuote();
await this.populateCustomers();
await this.populateEmployees();
await this.populatePropertyTypes();
}
and each of this functions are getting some data and setting some values in the state, now my problem is that every time one of the promises resolves it re-renders the page which I'd like to avoid, is there any way around this?

You should use Promise.all to ensure that all Promises in an array gets resolved before perform an operation
async componentDidMount(){
const calls = [call1(), call2(), call3()]
const results = await Promise.all(calls)
console.log(results[0],results[1],results[2])
}

Use Promise.all() to fetch all data and then this.setState() to perform the only re-render
async componentDidMount() {
const [quote, customers, employees, propertyTypes] = await Promise.all([
this.populateQuote(),
this.populateCustomers(),
this.populateEmployees(),
this.populatePropertyTypes()
]);
this.setState({
quote,
customers,
employees,
propertyTypes
});
}

What you could use is Promise.all, to initiate parallel requests for your async calls.
async componentDidMount() {
const [quotes, customers, employees, propertyTypes] = await Promise.all([
this.getQuotes(),
this.getCustomers(),
this.getEmployees(),
this.getPropertyTypes()
]);
}
And then you would set corresponding state based on your results. This can be achieved only if your calls are independent of other async results (if next is dependent on previous, you'll have to await each function and pass it required result from previous call).
Most important thing for your answer is that you should not call setState before fetching all required results, because each call to setState initiates new render.

Related

Axios request returning promise Object

Not sure why when I log out the price after executing the fetchPrice() method, I am returned a promise object instead of price. Please help :)
async function fetchPrice() {
await axios
.get(assetData(selectedAsset))
.then((response) => {
return response.data.market_data.current_price.eur;
})
.catch((error) => console.log(error.response.data.error));
}
useEffect(() => {
let price = fetchPrice();
console.log("Asset Price: " + price);
let assetTotal = Number(cost) / Number(price);
setAssetAmount(assetTotal);
}, [selectedAsset]);
The problem is how you are handling the await of your function. Normally when working with promises you use either await or a callback, not both. Because await already wait for the promise to resolve or throw an error, you directly use the return inside the function and not in the callback.
async function fetchPrice() {
try {
const price = await axios.get(assetData(selectedAsset));
return price;
} catch (e) {
console.log(error.response.data.error)
}
}
Using return inside the callback doesn't return the object you expect since it is the result of your callback function and not the original function.
Since fetchPrice is an async function it is normal that if you try to print it you will obtain a promise. This won't change even if you correct your function as I told you above. The only way to obtain the object is by awaiting your fetch price inside the useEffect (and by making the useEffect itself async) like this:
useEffect(async () => {
let price = await fetchPrice();
console.log("Asset Price: " + price);
let assetTotal = Number(cost) / Number(price);
setAssetAmount(assetTotal);
}, [selectedAsset]);
However, if you do it, you will obtain the following warning because your use effect use should be synchronous:
Effect callbacks are synchronous to prevent race conditions. Put the async function inside
The final solution? Set state directly inside the callback.
async function fetchPrice(cost) {
try {
const price = await axios.get(assetData(selectedAsset));
setAssetAmount(Number(cost)/Number(price))
} catch (e) {
console.log(error.response.data.error)
}
}
However, be careful of memory leaks when setting a state asynchronously inside a component that can be unmounted.
You need to use either .then() or async/await.
Example of then()
useEffect(() => {
axios.get("https://api.github.com/users/mapbox").then((response) => {
console.log(response.data);
});
}, []);
In above code, we are using .then() so then will be fired when api call is done.
Example of async/await
async function axiosTest() {
const response = await axios.get("https://api.github.com/users/mapbox");
console.log(response.data, "with await");
}
In above code, we are using await to wait until the api call is done.
Here is the Codesandbox to check the difference with live api call

How to fix multiple call fetch data in forEach() using React Hooks

In react Hooks, I am trying to fetch data from the API array but in the Foreach function, the API call causes infinity.
How to fix this?
const [symbols, setSymbols] = useState([]);
getPortfolioSymbolList(portfolio_name).then(data => data.json()).then(res => {
res.forEach((symbol_data)=>{
fetchPrice(symbol_data.symbol).then(price => {
setSymbols(price);
});
})
}
function fetchPrice(symbol){
const price = fetch(`api_url`)
.then(chart => chart.json())
return price;
}
Here, call fetchPrice() causes in infinite.
Setting the state will always cause a rerender
What happens in your code is the request is made and then the data is set causing a rerender. Then because of the rerender the request is made again and sets the state again and causes the rerender again.
If you have a request for data you probably want to put a React.useEffect so it only requests once.
React.useEffect(() => {
/* your data request and data set */
}, []); // the [] will only fire on mount.
Is is because your setSymbols call inside forEach makes component rerender (reload) - it means that all of your main component function is call again and again... getPortfolioSymbolList too. You have to use useEffect hook to resolve this problem. Your getPortfolioSymbolList() API call should be inside useEffect.
https://reactjs.org/docs/hooks-effect.html
PROBLEM
Your first symbol is updated in your API call, which triggers a re-render of the component calling the API call to go on an infinite loop.
SOLUTION
Wrap your API in your useEffect. The function inside your useEffect will only be called once. See useEffect docs here
You need to use for await of to loop asynchronously. forEach can't loop asynchronously. See for await of docs here
Update your symbols once all the data is collected.
function Symbols() {
const [symbols, setSymbols] = useState([]);
React.useEffect(() => {
async function fetchSymbols() {
const portfolio = await getPortfolioSymbolList(portfolio_name);
const jsonPortfolios = await data.json();
const symbols = [];
for await (let jsonPortfolio of jsonPortfolios) {
const price = await fetchPrice(jsonPortfolio.symbol);
symbols.push(price);
}
setSymbols(symbols);
}
fetchSymbols();
}, []);
return /** JSX **/
}

how to remove lag in setState using callback function in react

how to remove lag in setState using callback function in react
tried using callback but still lag state and data in array state cannot be mapped
mapfn(){
ServerAddr.get(`/dishes/read/meal/category`)
.then(res => {
const getmeal6 = res['data']['data'];
this.setState({ getmeal6 },()=>{
console.log('log233',this.state.getmeal6);
});
});
console.log('log232',this.state.getmeal6);
this.state.getmeal6.map((item) => {
return (
this.setState({
maparr:[...this.state.maparr,item.id],
})
);
});
console.log(this.state.maparr,'val32');
}```
in log233 the state is proper but in log232 the state lags with 1
The problem with your current code is that both http calls, and calls to setState are asynchronous.
// this call is asynchronous
ServerAddr.get(`/dishes/read/meal/category`)
.then(res => {
const getmeal6 = res['data']['data'];
// this is also asynchronous
this.setState({ getmeal6 },()=>{
});
});
// this call happens synchronously! It will almost certainly happen before the two
// async calls complete
this.state.getmeal6.map((item) => {
return (
this.setState({
maparr:[...this.state.maparr,item.id],
})
);
});
If you want to do something after your http call and the setState are both resolved, you need to either be inside the then function of a promise, or in the callback function of setState.
So something like this:
// this call is asynchronous
ServerAddr.get(`/dishes/read/meal/category`)
.then(res => {
const getmeal6 = res['data']['data'];
// this is also asynchronous
this.setState({ getmeal6 },()=>{
// this is where you need to put the
// code you want to happen after the http call and setState
});
});
That said, you need to reconsider what you are trying to do - either by refactoring your state management using something like Redux, or by using async await in your method, to make your code a little easier to read, or by a totally new approach to the problem at hand.

async/await is not working with react.js inside ComponentDidMount

I'm trying to fetch data using axios.
This code is working:
componentDidMount(){
const posts = axios.get("https://jsonplaceholder.typicode.com/posts");
console.log(posts);
}
but this doesn't:
async componentDidMount(){
const data = await axios.get("https://jsonplaceholder.typicode.com/posts");
console.log(data)
}
Any idea why it's not working?
There is nothig special in componentDidMount that could prevent it from being async. This particular issue looks like an Axios issue. It's pretty easy to check using fetch:
async componentDidMount() {
const data = await fetch("https://jsonplaceholder.typicode.com/posts");
console.log(await data.json())
}
It is not quite clear what you are referring to when saying "this code works":
The output in the console will not be your lists of posts, right?
But you don't need to use async/await, they are just syntactic sugar.
The value synchronously returned by the call to axios.get is a Promise so you can also use then and catch:
componentDidMount(){
axios.get("https://jsonplaceholder.typicode.com/posts")
.then(console.log)
.catch(console.error);
}
This way you you don't need async/await support in react which seems to be non trivial to set up correctly as this article states: https://www.valentinog.com/blog/await-react/
be aware that the component will render anyway since it not aware that you are still waiting for the data to load. So when the data loads and you updated your state it will need to render again.
Your both the snippets are working fine.
Your code,
componentDidMount(){
const posts = axios.get("https://jsonplaceholder.typicode.com/posts");
console.log(posts);
}
Here console.log(posts) will return only Promise and not actual data.
And using async/await,
async componentDidMount(){
const posts = await axios.get("https://jsonplaceholder.typicode.com/posts");
console.log(posts);
console.log(posts.data);
console.log(JSON.stringify(posts.data))
console.log(JSON.stringify(posts.data[0]));
}
Here also,
console.log(posts) - Will return Promise object and not actual data
console.log(posts.data) - This will return actual data array
console.log(JSON.stringify(posts.data)) - This will return stringified version of actual data
console.log(JSON.stringify(posts.data[0])) - This will return stringified version of first record in actual data.
Demo

Promises resolved before all inner promise resolved, multiple async API calls

Hi I'm new to the react/redux development environment so I appreciate any help.
I'm trying to make 2 API calls asynchronously when componentDidMount by calling dispatch(fetchAllPositions(selectedUserDivision)) in my app.js
I found a suggested method in this post and the fetchAllPositions function wraps two action functions together in a Promise.all
export function fetchAllPositions(division) {
return dispatch => Promise.all([
dispatch(fetchUserPositionsIfNeeded(division)),
dispatch(fetchDefaultPositionsIfNeeded(division))
])
.then(console.log("fetched both"))
}
The two action functions are nearly identical, they just call a slightly different API url. One of them looks like the follows, where the shouldFetchUserPosition is just a pure function that returns a boolean:
function fetchUserPositions(division) {
return dispatch => {
const url = apiOptions.server + `/api/user/position?division=${division}`
dispatch(requestUserPositions(division))
return fetch(url, options)
.then(response => response.json())
.then(json => dispatch(receiveUserPositions(division,json)),
err => console.log(err))
}
}
export function fetchUserPositionsIfNeeded(division) {
return (dispatch, getState) => {
if (shouldFetchUserPositions(getState(), division)) {
return dispatch(fetchUserPositions(division))
} else {
return Promise.resolve()
}
}
}
The logic is that for an update, a REQUEST... action is sent while pulling data, then a RECEIVE... action is sent when data is ready to be updated into the new state.
The trouble is that the Promise.all should wait for REQUEST1 REQUEST2 RECEIVE1 RECEIVE2 to all come in before doing the .then(console.log("fetched both"))
Right now, it's does the .then after first 2 REQUEST are finished, not waiting for the 2 RECEIVE to come in.
I suspect it's the nested nature of the requestUserPositions() within the function that wraps fetch()
The REQUEST action is a simple function, and in the reducer it just flips an isFetching property to true:
function requestUserPositions(division) {
return {
type: REQUEST_USER_POSITIONS,
division
}
}
Sorry for this long issue but I'd appreciate any suggestions.
This is a careless mistake
Turns out that when the .then() is wrapped inside a dispatch it gets executed simultaneously as the Promise.all() is being carried out.
The intended dispatch order was created by tagging the then(()=>console.log to the last dispatch dispatch(fetchAllPositions(selectedUserDivision)) done from ComponentDidMount

Resources