0

Salutations!

I am trying (and succeeding) in copulating an array inside an async function. I am using this array to set the state of an array declared on the top level of a React Component like so:

  const [retrievedData, setRetrievedData] = useState([]);

  useEffect(() => {
    setRetrievedData;
  }, [retrievedData]);

  async function fetchInfo() {
    const promiseData = await Promise.all(<My fetch links array>)
    );

    const dataInJson = await promiseData.map((resp) => resp.json());

    let actualData = [];

    for (let i = 0; i < dataInJson.length; i++) {
      const foo = await Promise.resolve(dataInJson[i]);
      actualData.push(foo);
    }

    setRetrievedData(actualData);
  }

  fetchInfo();

The problem with this code is that it creates an infinite loop of setStates, even when I set the useEffect second parameter to an empty array. I also tried using async/await when calling the fetchInfo function but that just returns another Promise of course. While working on this, I also noticed that the Promise.all call runs twice.

I appreciate the time in reading this question.

Thanos Dodd
  • 572
  • 1
  • 4
  • 14

2 Answers2

1

Do something like this:

const [retrievedData, setRetrievedData] = useState({ data: [] });

const fetchInfo = useCallback(async () => {
  // do your stuff here...

  setRetrievedData({ data });
}, []);

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

If you intend to append data to retrievedData, then you can call setRetrievedData like this:

setRetrievedData(prevState => ({
  data: [...prevState.data, newData]
}));

If you're using fetchInfo only during component load, you can move its definition (without useCallback) to your useEffect and pass an empty dependency array to it instead as shown by the other answer.

brc-dd
  • 10,788
  • 3
  • 47
  • 67
  • This will couse infinite loop since `fetchInfo` is new reference to function, `useEffect` will run each time calling `fetchInfo`, and each `fetchInfo` will cause rerender by setting `setState` – jkaczmarkiewicz Nov 06 '21 at 08:02
  • @Jan Yeah, fixed that. Refer the updated answer. – brc-dd Nov 06 '21 at 08:06
0

This is common pattern in react

const Component = () => {
    const [data, setData] = useState();
    useEffect(()=>{
        const fetchData = async () => {
            const data = await fetch();
            setData(data);
        }
        fetchData();
    }, [])
    return <div>{JSON.stringify(data)}</div>
}

jkaczmarkiewicz
  • 1,500
  • 6
  • 17
  • This works nicely thank you. Since the data is updated using state it's not a real issue, but is there a reason this seems to render twice? Even when I was just logging the data inside the fetchData function, I get undefined first, then the desired result – Thanos Dodd Nov 06 '21 at 08:26
  • First time component renders without data (you can show spinner for example) After that useEffect will run and it will call fetch and when promise resolves it will call setState After calling setState component will update this time with data – jkaczmarkiewicz Nov 06 '21 at 08:37
  • It is normal. After call fetch, setData renders component again – Oğulcan Karayel Nov 06 '21 at 08:40
  • In the first render of component, data is undefined you can use show loading or something like that. It is a common pattern. It is nothing to worry about that because it isn't unnecessary render. – Oğulcan Karayel Nov 06 '21 at 08:52