Search code examples
reactjsasync-awaitreact-hooksuse-effectuse-state

useEffect doesn't run after rendering


I'm kind of confused about how useEffect is triggered and how it work. I wrote a function like this but the useEffect doesn't run at all. I want to fetch the data from the API and then render a page based on the data. But it doesn't trigger the useEffect. If I don't use the useEffect, it will render the page three times.

async function getData() {
  var tmpArrData = [];
  await fetch("this API is hidden due to the privacy of the company - sorry")
    .then((res) => res.json())
    .then((data) => {
      console.log("data", data);
      tmpArrData = data;
  });
  console.log("tmpData ", tmpArrData);
  return tmpArrData;
}

function App() {

  const [arrData, setArrData] = useState();
  const [loadData, setLoadData] = useState(false);

  useEffect(() => {
    console.log("if it works, this line should be shown");
    const tmpArrData = getData();
    setArrData(tmpArrData);
  }, [arrData]);


  const data = arrData[0];
  console.log(data);

  return (
      <GifCompoment 
      id = {data.id}
      name = {data.name}
      activeTimeTo = {data.activeTimeTo}
      activeTimeFrom = {data.activeTimeFrom}
      requiredPoints = {data.requiredPoints}
      imageUrl = {data.imageUrl}
      />
  );
}

export default App;


Solution

  • The useEffect hook is guaranteed to run at least once at the end of the initial render.

    getData is an async function and the useEffect callback code is not waiting for it to resolve. Easy solution is to chain from the implicitly returned Promise from getData and access the resolved value to update the arrData state. Make sure to remove the state from the useEffect's dependency array so that you don't create a render loop.

    The getData implementation could be clean/tightened up by just returning the fetch result, no need to save into a temp variable first.

    async function getData() {
      return await fetch(".....")
        .then((res) => res.json());
    }
    
    useEffect(() => {
      console.log("if it works, this line should be shown");
      getData().then((data) => {
        setArrData(data);
      });
    }, []); // <-- empty dependency so effect called once on mount
    

    Additionally, since arrData is initially undefined, arrData[0] is likely to throw an error. You may want to provide valid initial state, and a fallback value in case the first element is undefined, so you don't attempt to access properties of an undefined object.

    const [arrData, setArrData] = useState([]);
    

    ...

    const data = arrData[0] || {}; // data is at least an object
    
    return (
      <GifCompoment 
        id={data.id}
        name={data.name}
        activeTimeTo={data.activeTimeTo}
        activeTimeFrom={data.activeTimeFrom}
        requiredPoints={data.requiredPoints}
        imageUrl={data.imageUrl}
      />
    );