5
votes

I'm trying to rebuild some class components I have as functional components using React hooks to do the fetch.

I can fetch the data, this is fine, but it's calling to my API every few milliseconds, I just want it to fetch the data and stop as it would have in the class component with componentDidMount.

I think I can see why it's doing it (the fetchData() inside the function means it will just keep calling itself) but I'm not sure how to fix it.

I tried adding a setInterval, changing the order, and moving the fetchData() outside the function (the component wouldn't render because it was then undefined).

const MyFunction = () => {

const [apiResponse,setApiResponse] = useState({})

  async function fetchData() {

    const res = await fetch(`http://localhost:9000/example`,{
      method: 'POST',
      mode: 'cors',
      body: JSON.stringify({date: '20190707'}),
      headers: {'content-type': 'application/json',
      credentials: 'include'
    }
    })
  res
  .json()
  .then(res => setApiResponse(res))
  }

useEffect(() => {

  fetchData();

});


var queryResult = apiResponse['units']


return (
  <React.Fragment>  
    <CardHeader color={"info"} stats icon>
    <CardIcon color={"info"}>
    <Icon>{"trending_up"}</Icon>
    </CardIcon>
    <p className={''}>Drop In</p>
      <h3 className={''}>
        {queryResult} <small>Units</small>
      </h3>
    </CardHeader>
    <CardFooter ></CardFooter>
  </React.Fragment>        
);

}
2
You need to tell useEffect what its dependencies are, see e.g. stackoverflow.com/questions/53070970/infinite-loop-in-useeffect. This is covered in the docs too: reactjs.org/docs/hooks-effect.html.jonrsharpe

2 Answers

14
votes

Try adding an empty array at the end of your useEffect:

useEffect(() => {

  fetchData();

}, []);

React Docs: https://reactjs.org/docs/hooks-effect.html

Note

If you use this optimization, make sure the array includes all values from the component scope (such as props and state) that change over time and that are used by the effect. Otherwise, your code will reference stale values from previous renders. Learn more about how to deal with functions and what to do when the array changes too often.

If you want to run an effect and clean it up only once (on mount and unmount), you can pass an empty array ([]) as a second argument. This tells React that your effect doesn’t depend on any values from props or state, so it never needs to re-run. This isn’t handled as a special case — it follows directly from how the dependencies array always works.

If you pass an empty array ([]), the props and state inside the effect will always have their initial values. While passing [] as the second argument is closer to the familiar componentDidMount and componentWillUnmount mental model, there are usually better solutions to avoid re-running effects too often. Also, don’t forget that React defers running useEffect until after the browser has painted, so doing extra work is less of a problem.

We recommend using the exhaustive-deps rule as part of our eslint-plugin-react-hooks package. It warns when dependencies are specified incorrectly and suggests a fix.

2
votes

What you're doing is fine - you just need to add an empty dependencies array to your useEffect implementation (explanation below), like so -

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

When you pass an array to useEffect, you're actually telling React to only run your effect if one of the values in the array changes.

We are using an empty array as a dependency array because it is causing the effect to simulate the ComponentDidMount lifecycle event we all know and love in Class Components, running only when the component mounts rather than after every render.

You can use useEffect to simulate other lifecycle events such as ComponentWillUnmount, ComponentDidUpdate etc. I would suggest reading more about the useEffect hook in the docs.