2

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;

Hoangdz
  • 152
  • 1
  • 14
  • The `useEffect` hook is guaranteed to run at least once at the end of the initial render. Are you saying you don't see the `console.log("if it works, this line should be shown");` log? – Drew Reese Jan 30 '22 at 07:19
  • yes, I didn't see that line – Hoangdz Jan 30 '22 at 07:24

4 Answers4

5

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}
  />
);
Drew Reese
  • 165,259
  • 14
  • 153
  • 181
1

You should call state setter insede of Promise

function App() {
    const [arrData, setArrData] = useState();

    function getData() {
        fetch("/api/hidden")
            .then((res) => res.json())
            .then((data) => setArrData(data));
    }

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

    return ...
}
Artyom Vancyan
  • 5,029
  • 3
  • 12
  • 34
1

By combining the answer from Drew Reese and Artyom Vancyan, I have solved my problem. I think the key points are setState right in the then function .then((data) => setArrData(data)) ,don't put the dependency in the useEffect, and await inside the useEffect. Thank you guy super ultra very much. Big love

useEffect(() => {
    console.log("if it works, this line should be shown");
    const getData = async () => {
      await fetch("hidden API")
      .then((ref) => ref.json())
      .then((data) => {
        setArrData(data);
      });
    }
    getData();
  }, []);
Hoangdz
  • 152
  • 1
  • 14
  • Secondly, the console log (if it works, this line should be shown) doesn't work because the error while rendering. When I resolve the error, that line was shown – Hoangdz Jan 30 '22 at 07:41
-2
function App() {

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

  
  const async 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);
      setArrData(tmpArrData);
  });
  console.log("tmpData ", tmpArrData);
  return tmpArrData;
}

  useEffect(() => {
    console.log("if it works, this line should be shown");
    const callApi =async()=>{
      await getData();
    } 
  }, [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;

Page will be rendered three to four times it's normal.

Shah Vipul
  • 625
  • 7
  • 11
  • "Page will be rendered three to four times it's normal." That's not at all normal and will lead to performance issues as you scale. – cEeNiKc Jan 30 '22 at 07:28
  • https://stackoverflow.com/questions/48846289/why-is-my-react-component-is-rendering-twice So that might help – Shah Vipul Jan 30 '22 at 07:34
  • This post has nothing to do with the question. Number of rerenders will always depend on the update in state and props of a component. And if your component is rerendering 3-4 times when you just want to fetch data once, there is some issue with the component. Assuming page rendering 3-4 times is normal is not what you should go for and actually look for the issue. – cEeNiKc Jan 30 '22 at 07:37
  • Also the solution you have proposed would lead to an infinite loop – cEeNiKc Jan 30 '22 at 07:38
  • useEffect has deps of [] so this only happens on the first render only. Then you are changing state 2 times, so a re-render happens. This does not mean that the DOM is changed 3 times. – Shah Vipul Jan 30 '22 at 07:45
  • You have `arrData` as deps and you are calling `setArrData` inside useEffect which will update `arrData` and cause useEffect to run again and this process will keep on going and lead to infnite loop. I didn't say your solution will lead to DOM changing 3 times, you said that page rendering 3-4 times is normal. And you are not even calling `callApi` so actually nothing will happen at all. Have you tried running your own solution? – cEeNiKc Jan 30 '22 at 07:48
  • No, It will not lead to infinite loop because of this in useEffect [arrData]. Just run useEffect with [arrData] and without [arrData]. You will see diffrence. – Shah Vipul Jan 30 '22 at 07:50
  • Yes it will if you actually call `callApi`. – cEeNiKc Jan 30 '22 at 07:52
  • Quick example - https://codesandbox.io/s/mmkck . Check the console – cEeNiKc Jan 30 '22 at 07:55
  • put a console.log in app.js and check the console – Shah Vipul Jan 30 '22 at 08:05