0

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));
    })();
}, []);
muth
  • 41
  • 6
  • States are processed mostly in batches, asynchronously. Perhaps the easiest thing would be to return `reformatPrices(data)` from each call, set the recommendations based on that data, and _then_ set the prices states. That way everything's up-to-date and you don't need to use another `useEffect`. – Andy Oct 20 '21 at 18:59
  • Just wrap those requests with Promise.all – Dmitriy Mozgovoy Oct 20 '21 at 23:53

2 Answers2

1

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>
Siva K V
  • 10,561
  • 2
  • 16
  • 29
  • `async/await` could be removed as they're unnecessary and forces sequential fetches instead of parallel requests. – Emile Bergeron Oct 20 '21 at 19:41
  • I am getting the following error when I try this solution: "index.js:1 Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function." – muth Oct 20 '21 at 19:58
  • @muth See [Can't perform a React state update on an unmounted component](https://stackoverflow.com/q/53949393/1218980) – Emile Bergeron Oct 20 '21 at 20:08
  • @EmileBergeron thank you for your help, but it seems like none of the fixes mentioned in that thread is working for me. – muth Oct 20 '21 at 20:39
  • @EmileBergeron actually, after some digging I managed to fix it, the unmounted issue thank you! However, I still have the original issue of coinbasePrices and binancePrices being empty in calculateRec – muth Oct 20 '21 at 20:56
  • @muth, Try run the above code snippet – Siva K V Oct 20 '21 at 21:12
1

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.

knightburton
  • 1,114
  • 9
  • 22
  • I am getting the following error when I try this solution: "index.js:1 Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function." – muth Oct 20 '21 at 20:39
  • Actually, I believe I fixed that error, but I am still getting an error in calculateRec because coinbasePrices and binancePrices are empty. – muth Oct 20 '21 at 20:53
  • 2
    `useCallback` is redundant here :) – Dmitriy Mozgovoy Oct 20 '21 at 23:58
  • @muth if your inner logic is broken inside `calculateRec` then can you share that logic please? – knightburton Oct 22 '21 at 08:51