1

I'm writing an asynchronous function in React that should set the "coords" state to an array of objects containing US states and their respective latitudes/longitudes. To do this, I'm making an API call and resolving those calls using Promise.all. On the first refresh of the page, the function works as intended. However, on subsequent refreshes, the function does not execute the Promise.all statement and therefore does not set the coords state. Why is this the case and how can I resolve this issue?

export default function App() {

  const [covidDataJSON, setCovidDataJSON] = useState(null);
  const [stateNames, setStateNames] = useState([]);
  const [coords, setCoords] = useState([]);
  const [stateInfos, setStateInfos] = useState([]);

  useEffect(() => {
    fetchCoords();
  }, [])

  const fetchCoords = async () => {
    try {
      getStateNames();
      const req = await Promise.all(stateNames.map(async (state) => {
        return await axios.get(`https://nominatim.geocoding.ai/search.php?state=${state}&format=jsonv2`);
      }))
      for (let i = 0; i < req.length; i++){
        const stateInfo = req[i].data[0];
        if (req[i].data.length !== 0)
          setCoords(coordsArr => [...coordsArr, {name: stateInfo.display_name, lat: stateInfo.lat, lon: stateInfo.lon}]);
      }
    } catch (err) {
      console.error(err);
    }
  };

  const getStateNames = () => {
    try {
      const stateArr = [];
      for (let i = 0; i < states.length; i++){
        stateArr.push(states[i].name);
      }
      setStateNames(stateArr);
    } catch (error) {
      console.error(error);
    }
  }
  • 1
    When you say "refresh", do you mean a browser refresh (like you're reloading the page itself)? – Cully May 01 '22 at 21:02
  • Yes, I mean like a full page reload. – rafaelsinger May 01 '22 at 21:04
  • Are the requests going through (i.e. in the Chrome dev tools Network tab)? Are you getting rate-limited? – Cully May 01 '22 at 21:34
  • It's not clear where `states` is coming from. Is it actually the same after your "refresh"? Also, the `getStateNames()` call [is not going to update the `const stateName`](https://stackoverflow.com/q/54069253/1048572), so `stateNames` is always the empty array when your effect runs. – Bergi May 01 '22 at 22:51

2 Answers2

3

I think your promise.all does not execute because stateNames is still an empty array.

You need to define 2 useEffect hooks as follows. -

// this will trigger function fetchCoords once your have set the stateNames.
useEffect(() => {
    if(!stateNames || !stateNames.length){
        return;
    }
    fetchCoords();
}, [stateNames]);

// this hook will set your stateNames once the component loads.
useEffect(() => {
    getStateNames();
}, [])

Also, I don't see the states variables used in getStateNames defined in above sample code. Also remove getStateNames() call from inside of fetchCoords() Let me know if the above works for you and try logging stateNames to verify this.

Sahil Jain
  • 450
  • 1
  • 8
  • 19
1
  1. Inside getStateNames(), you're calling setStateNames(stateArr); setter function for a state is async. You're mapping over stateNames inside fetchCoords(). There is a possibility that stateNamesstate is not updated when map loop is run. Thats why I return stateArr from getStateNames() and use that value inside fetchCoords().

  2. Inside fetchCoords(), you've added the following code


const req = await Promise.all(stateNames.map(async (state) => {
        return await axios.get(`https://nominatim.geocoding.ai/search.php?state=${state}&format=jsonv2`);
      }))

There is no need to await axios.get() here because you're already using Promise.all to await for promises to resolve, that's why I've removed await axios.get() from .map and simply returning an array of promises for Promise.all()

Give this a try. I think this should work.


export default function App() {
  const [covidDataJSON, setCovidDataJSON] = useState(null);
  const [stateNames, setStateNames] = useState([]);
  const [coords, setCoords] = useState([]);
  const [stateInfos, setStateInfos] = useState([]);

  useEffect(() => {
    fetchCoords();
  }, []);

  const fetchCoords = async () => {
    try {
      const stateArr = getStateNames();
      const req = await Promise.all(
        stateArr.map((state) => {
          return axios.get(
            `https://nominatim.geocoding.ai/search.php?state=${state}&format=jsonv2`
          );
        })
      );

      for (let i = 0; i < req.length; i++) {
        const stateInfo = req[i].data[0];
        if (req[i].data.length !== 0)
          setCoords((coordsArr) => [
            ...coordsArr,
            {
              name: stateInfo.display_name,
              lat: stateInfo.lat,
              lon: stateInfo.lon
            }
          ]);
      }
    } catch (err) {
      console.error(err);
    }
  };

  const getStateNames = () => {
    try {
      const stateArr = [];
      for (let i = 0; i < states.length; i++) {
        stateArr.push(states[i].name);
      }
      setStateNames(stateArr);
      return stateArr;
    } catch (error) {
      console.error(error);
    }
  };

  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <h2>Start editing to see some magic happen!</h2>
    </div>
  );
}


Inder
  • 1,711
  • 1
  • 3
  • 9