Amaan Kulshreshtha
Amaan Kulshreshtha

Reputation: 560

Why doesn't a function dependency in hooks cause infinite renders

Following is the snippet link to codesandbox:

// function getFetchUrl(query) {
//   return "https://hn.algolia.com/api/v1/search?query=" + query;
// }
function App() {
  const [reactResult, setReactResult] = useState(null);
  const [reduxResult, setReduxResult] = useState(null);
  function SearchResults() {
    // 🔴 Re-triggers all effects on every render
    // const getFetchUrl = useCallback((query) =>  {
      // return "https://hn.algolia.com/api/v1/search?query=" + query;
    // }, []);


     function getFetchUrl(query) {
       return "https://hn.algolia.com/api/v1/search?query=" + query;
     }

    useEffect(() => {
      console.log("running effect: 15");
      setReactResult(getFetchUrl("react"));
      // ... Fetch data and do something ...
      // }, [getFetchUrl]); // 🚧 Deps are correct but they change too often
    }, [getFetchUrl]);

    useEffect(() => {
      console.log("running effect: 21");
      setReduxResult(getFetchUrl("redux"));
      // ... Fetch data and do something ...
      // }, [getFetchUrl]); // 🚧 Deps are correct but they change too often
    }, [getFetchUrl]);

    // ...
  }

  SearchResults();

  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <h2>{reactResult}</h2>
      <h2>{reduxResult}</h2>
    </div>
  );
}

The output in the console is

running effect: 15
running effect: 21
running effect: 15
running effect: 21

I have checked out the this answer, and I get it that the functions get re-defined which causes the useEffect to run again(for the second time). But I want to clarify one doubt:

When the useEffect runs for the second time, it calls the stateSetter functions(which asks React to render the component again).

So shouldn't the above snippet run in an infinite loop?

Example and basic understanding picked up from A Complete Guide to useEffect

Upvotes: 2

Views: 131

Answers (2)

Jonas Wilms
Jonas Wilms

Reputation: 138267

Your effects are only triggered when getFetchUrl changes ... and as it is a memoized callback (that does not get changed) the effects only run once.

Upvotes: 0

Hargo
Hargo

Reputation: 1266

When using useState react is smart enough to skip re-rendering if the state value hasn't actually changed despite a call to the setState function. (This is documented at https://reactjs.org/docs/hooks-reference.html#bailing-out-of-a-state-update)

The example code you have included is slightly different from the linked code pen and will actually result in only a single "set" of console log message.

running effect: 15
running effect: 21

the sequence of events is:

  1. Initial render, triggers both effects and updates both the reactResult and reduxResult state. This enqueues a re-render.
  2. The component re-renders. In your included example you're using useCallback with no dependencies which will return the previous value, and therefore the the effects will not run.

On the other hand, in your code pen, instead of useCallback you redefine the callback on each execution, in that case you will get two "sets" of console message:

  1. Initial render, triggers both effects and updates both the reactResult and reduxResult state. This enqueues a re-render.
  2. The component re-renders. getFetchUrl is a local funtion and so is not equal to getFetchUrl from the previous run. As a result the effects will re-run. However setReactResult and setReduxResult are both called with the same value as before, so a re-render will not be triggered.

Upvotes: 3

Related Questions