I am new to React, and I am currently trying to figure out a way to call a function only after two state variables have been updated. These state variables are updated after running fetch on my backend, but the function I am trying to call keeps returning an error because it is being called before the variables have been set. I have been trying to useEffect and async, but I feel like I am missing something. I want to call "calculateRec" after coinbasePrices and binancePrices has been set using the data from fetch. Any help would be greatly appreciated!
const [coinbasePrices, setCoinbasePrices] = useState([]);
const [binancePrices, setBinancePrices] = useState([]);
const [recommendations, setRecommendations] = useState({});
useEffect(() => {
(async () => {
await fetch('http://localhost:8080/prices/coinbase').then(res => res.json())
.then((data) => {
setCoinbasePrices(reformatPrices(data))
})
.catch(err => { throw err });
await fetch('http://localhost:8080/prices/binance').then(res => res.json())
.then((data) => {
setBinancePrices(reformatPrices(data))
})
.catch(err => { throw err });
setRecommendations(calculateRec(coinbasePrices, binancePrices));
})();
}, []);
Use an seperate useEffect with dependency array as first two state values.
Update: based suggestions (Thank you) updated with working example using swapi.
const Component = () => {
const [people, setPeople] = React.useState({});
const [planets, setPlanets] = React.useState({});
const [data, setData] = React.useState({});
React.useEffect(() => {
fetch("https://swapi.dev/api/people/1/")
.then(res => res.json())
.then(setPeople)
fetch("https://swapi.dev/api/planets/1/")
.then((res) => res.json())
.then(setPlanets)
}, []);
React.useEffect(() => {
if (people && planets) {
setData({ person: people.name, planet: planets.name});
}
}, [people, planets]);
return <div>{JSON.stringify(data)}</div>
}
ReactDOM.render(<Component />, document.getElementById("app"));
<script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
<div id="app"></div>
I think you have multiple issues. First, decide whether you suing promise or async await combination. Then make sure your hooks are defined properly.
I would use an async hook for each fetch then a single useEffect to calculate the final values.
This would be the use fetch hook:
const useFetch = (url, formatter) => {
const [loading, setLoading] = useState(false);
const [result, setResult] = useState(null);
const [error, setError] = useState(null);
const execute = async () => {
setLoading(true);
setResult(null);
setError(null);
try {
const response = await fetch(url);
const data = response.json();
const result = formatter ? formatter(data) : data;
setResult(result);
setLoading(false);
} catch (error) {
setError(error);
setLoading(false);
}
});
useEffect(() => {
execute();
}, [execute]);
return { loading, result, error };
};
and based on that this would be your base logic:
const YourFunctionalComponent = () => {
const [recommendations, setRecommendations] = useState({});
const { result: coinbasePrices } = useFetch('http://localhost:8080/prices/coinbase', reformatPrices);
const { result: binancePrices } = useFetch('http://localhost:8080/prices/binance', reformatPrices);
useEffect(() => {
if (coinbasePrices && binancePrices) setRecommendations(calculateRec(coinbasePrices, binancePrices));
}, [coinbasePrices, binancePrices]);
return (
// use your recommendations as you wish...
);
};
With this you can use the individual fetch errors as you wish alongside with loadings as well to display loading indicator if you wish.