React Hook setting state in useEffect with state in dependency array - reactjs

I have a question about the correct usage with useEffect and setState in React Hooks.
here is my react app that gets a persisted configuration and some other data depending on the config like this:
function App() {
const [config, setConfig] = useState(null);
// some other states
useEffect(() => {
const fetchData = () => {
axios.get("/path/to/config").then(response => {
if (response.status === 200) {
return response.data
}
})
.then(data => {
setConfig(data.config);
// set some other states
})
.catch(error => {
console.log("Error:" + error);
})
}
fetchData();
}, [config]);
return (
<div >
...
</div>
);
}
If I run this App useEffect is instantly called twice one time for first render and then a second time because setConfig(data.config); is called in the axios get success function.
The user has the possibility to change his own config that is done in another request. If he does I want after state config changes that the config and some other data depending on the config is reloaded via this useEffect function.
Now since there is no setstate callback in react hooks I read somewhere I should use useEffect with the state variable in the dependency array.
How can I prevent that my useEffect is called twice right at the beginning?
I have the feeling that I am doing it all wrong.
Thank you in advance

You need to add an if condition in useEffect, because useEffect is called by default on first render.
useEffect(() => {
if(config !== null){
const fetchData = () => {
axios.get("/path/to/config").then(response => {
if (response.status === 200) {
return response.data
}
})
.then(data => {
setConfig(data.config);
// set some other states
})
.catch(error => {
console.log("Error:" + error);
})
}
fetchData();
}
}, [config]);

This is not right!
I guess this can call fetchData infinitely.

Related

UseEffect unable to update useState properly

I am trying to update this state that is an empty list on start:
const [winnerList, setWinnerList] = useState([]);
from a useEffect which will run once:
useEffect(()=>{
fetch("/players").then( res => res.json()).then(data => {
if(data) {
console.log(data);
setWinnerList(JSON.parse(data));
console.log(winnerList);
window.localStorage.setItem('winner', JSON.stringify(winnerList));
}
})
},[])
when I console.log (data) I get the json as expected but when I console log(winnerList) I get an empty array even though I setWinnerList with the json data.
after a
This is because setWinnerList is asynchronous. You can change the logic like this :
useEffect(() => {
fetch("/players").then(res => res.json()).then(data => {
if (data) {
console.log(data);
setWinnerList(JSON.parse(data));
}
})
}, []);
useEffect(() => {
console.log(winnerList);
window.localStorage.setItem('winner', JSON.stringify(winnerList));
}, [winnerList]);
First winnerList should be updated, and after the new render has been occured, useEffect callback with winnerList dependency gets called.
Check out react documentation about useEffect and useState and also there are a bunch of very good examples in beta.reactjs about useEffect hook.

Add object array to setState

I'm trying to add an object array in the state series. With this code the useEffect function get stuck in an infinite loop. How can I solve this? Without adding the series const as parameter I get the error about a missing dependency and the code will only run on startup.
import React, { useState, useEffect } from "react";
const LineChart = () => {
const [series, setSeries] = useState([]);
useEffect(() => {
const url = "http://localhost:4000";
const fetchData = async () => {
try {
fetch(url, {
method: "GET",
})
.then((response) => response.json())
.then((data) => {
let chartData = data.testRunSummaries
.map(function (testrun) {
return {
duration: testrun.endTime - testrun.startTime,
label: testrun.testSetName + "#" + testrun.userFacingId,
testrun: testrun.testRunId,
status: testrun.status,
};
});
setSeries(chartData, ...series);
console.log(series);
});
} catch (error) {
console.log(error);
}
};
fetchData();
}, [series]);
return (
...
);
};
export default LineChart;
series is in your useEffect dependency array. And your useEffect is changing series. So obviously you'll be stuck in a infinite loop.
You don't need your series to be set as a dependency for useEffect.
As your useEffect will only be trigger once on mount, you can just have
setSeries(chartData);
And if you really need to have former values of series, you should use
setSeries(series => [...chartData, ...series]);
Moreover, seeing your
setSeries(chartData, ...series);
console.log(series);
Let me remind you that setState is async there is no way this will log your updated state :)

useEffect race condition with Redux

Hello I have been struggling to set values when mounting a component. I am using useEffect Hook and useDispatch, useSelector for calling methods and also getting the state from the store. The problem is that the state from the store is delayed 1 render and therefore I need to run two times the code inside useEffect in order to get the behavior I expect. Which is -> when the component loads, do an API call and list some documents.
Data declaration
const data = useSelector(state => state.whole.manufactured);
useEffect code
useEffect(() => {
if (counter <= 1) {
fetchData();
setProducts(data);
setCounter(counter + 1);
}
console.log('data in store', data);
console.log('useEffect');
}, [clickedItem, data]);
fetchData function
const fetchData = async () => {
await dispatch(get_manufacturing());
};
get_manufacturing
return dispatch => {
dispatch(Actions.uiStartLoading());
fetch('http://myapi/api/product/get-products', {
method: 'GET',
headers: Interceptor.getHeaders(),
})
.then(res => res.json())
.then(result => {
dispatch(Actions.uiStopLoading());
if (result.status === true) {
dispatch({type: TYPES.GET_MANUFACTURABLE, data: data});
}
})
.catch(error => {
dispatch(Actions.uiStopLoading());
console.log(error);
});
When this code runs, the following happens.
As you can see in the first render it seems it just completely ignorees the fetchData() and proceeds to the console.log, after the second render the values have been properly set. How can I resolve this issue is there something I'm not getting properly done?
Replace clickedItem in the dependency list with counter.

How to get value of `var` inside return of React component?

I'm trying to get the value of googleToken inside a <div> in the return of my React Component. The value is already updated, but it's the initial state here in the return, therefore, it always shows null
const Layout = props => {
let googleToken = null;
useEffect( () => {
fetchGoogleToken();
}, [])
const fetchGoogleToken = async () => {
await api
.get("/google_token")
.then((response) => {
console.log('google_token: ' + response.data.google_token);
googleToken = response.data.google_token;
console.log('google_token updated: ' + googleToken);
})
.catch((error) => console.log(error));
};
const getGoogleToken = (res) => {
console.log(res);
setGoogleToken(res.accessToken);
saveGoogleTokenInDB();
};
const saveGoogleTokenInDB = async () => {
await api
.post("/fit-auth", {google_token : googleToken})
.then((response) => {
console.log(response);
})
.catch((error) => console.log(error));
};
return (
<div className={classes.googleButton} style={{display: googleToken === null ? 'block' : 'none'}}>
<h3>{googleToken}</h3>
<div/>
}
Any ideas on why I can't get the updated value?
It is right to use useEffect hook for fetching. But the result must be kept into state. And when you ask react to update state, you can not watch changes on the next line of code using console.log because setState is async function and it will be executed later on.
// this will never work as you might be expected:
setState(newState)
console.log(state)
To catch state updates always use useEffect hook as in example below:
useEffect(() => {
console.log(state)
}, [state])
Also, avoid using inline styles for showing / hiding your components. Check the official conditional rendering recommendations.
The final code is going to look like this:
const Layout = props => {
const [googleToken, setGoogleToken] = useState(null)
useEffect( () => {
fetchGoogleToken();
}, [])
// you can watch how state changes only using useEffect:
useEffect(() => {
console.log('google_token updated: ' + googleToken)
}, [googleToken])
const fetchGoogleToken = async () => {
await api
.get("/google_token")
.then((response) => {
console.log('google_token: ' + response.data.google_token);
setGoogleToken(response.data.google_token);
})
.catch((error) => console.log(error));
};
// conditional rendering:
if (!googleToken) return <span>Fetching token...</span>
return (
<div className={classes.googleButton}>
<h3>{googleToken}</h3>
<div/>
)
}
Hope you will find this helpful.
The issue is that when the value of googleToken is updated the component is not notified about this. Since the component is not notified about the change it still believes that the value is still the initial value and it does need to change anything in DOM.
To solve this issue try using states or just force a rerender once the function is executed.

Variable doesn't initialize after async function

I am making a request like this:
const createProgramari = async () => {
let prog = [];
try {
await axios.get('http://localhost:8000/api/programariByDentistID', {
params: {
id: JSON.parse(localStorage.getItem('user'))["id"]
}
})
.then(function (res) {
console.log(res);
if(res.status === 200) {
prog = res.data;
}
})
.catch(function (error) {
console.log(error);
});
setProgramari(prog);
} catch(e) {
console.log(e);
}
}
If I try to see this in my useEffect the variable 'programari' is an empty array (the value I initialized it with)
const [programari, setProgramari] = useState([]);
useEffect( () => {
// code to run on component mount
createProgramari();
console.log(programari);
}, [])
I tried printing the response and axios gets it right.
What am I doing wrong? Is there a place I could learn how to not do the same mistake?
The salient point here is that setProgramari is async in nature. If it is called, it doesn't necessarily imply that it will be executed right away. One way you can handle this is follows.
useEffect(() => {
createProgramari();
}, []);
// consider useMemo or useCallback based on your use case if need be
useEffect(() => {
// whatever logic you want to have
console.log(programari); // will get new value always
}, [programari])
The way you wrote the function is very confusing, I'd suggest refactoring this to
const createProgramari = async () => {
try {
const prog = (await axios.get('http://localhost:8000/api/programariByDentistID', {
params: {
id: JSON.parse(localStorage.getItem('user'))["id"]
}
})).data;
setProgramari(prog);
} catch (e) {
console.log(e)
}
}
To view data you can do something like this:
useEffect(() => {
createProgramari()
.then((someReturnedDataFromAPI) => { // Promise returned
console.log(programari)
})
}, []) // Initialization
in useEffect programari is equal with [] because setProgramari() not update already existed state in current component version, if set new state for programari, this modification propagate rerendering component
console.log(programari) work with current component state version
if you want dump programari you can move console.log outsite useEffect
in this case you get in console two dump - [] for first version and [ withData ] for version what rerender because setState()
or if you want use data from axios in useEffect you can return it how promise

Resources