Why is an infinite loop created when I pass a function expression into the useEffect dependency array? The function expression does not alter the component state, it only references it.
// component has one prop called => sections
const markup = (count) => {
const stringCountCorrection = count + 1;
return (
// Some markup that references the sections prop
);
};
// Creates infinite loop
useEffect(() => {
if (sections.length) {
const sectionsWithMarkup = sections.map((section, index)=> markup(index));
setSectionBlocks(blocks => [...blocks, ...sectionsWithMarkup]);
} else {
setSectionBlocks(blocks => []);
}
}, [sections, markup]);
If markup altered state I could understand why it would create an infinite loop but it does not it simply references the sections prop.
So I'm not looking for a code related answer to this question. If possible I'm looking for a detailed explanation as to why this happens.
I'm more interested in the why then just simply finding the answer or correct way to solve the problem.
Why does passing a function in the useEffect dependency array that is declared outside of useEffect cause a re-render when both state and props aren't changed in said function?
The useEffect hook allows you to perform side effects in a functional component. There is a dependency array to control when the effect should run. It runs when the component is mounted and when it is re-rendered while a dependency of the useEffect has changed.
Dependency arrays are a concept that is tightly coupled to hooks in React (thus also to function components). Some hooks, like useEffect and useCallback have 2 arguments. The first one is a callback (a function), and the second one is the dependency array. It takes the form of an array of variables.
To pass an array to useEffect dependency list in React, we can pass in the array state into the dependencies list. We have the nums array state we created with the useState hook. Then we call useEffect with a callback that logs the current value of nums .
The "react-hooks/exhaustive-deps" rule warns us when we have a missing dependency in an effect hook. To get rid of the warning, move the function or variable declaration inside of the useEffect hook, memoize arrays and objects that change on every render or disable the rule.
The dependency array is the second optional argument in the useEffect function. As the name implies, it is an array of dependencies that, when changed from the previous render, will recall the effect function defined in the first argument. Let’s first look at an example:
What Is useEffect? In the simplest terms, useEffect is a Hook that allows you to perform side-effects in functional components. For even more detail, these effects are only executed after the component has rendered, therefore not blocking the render itself.
Even though usage of the useEffect Hook is common in the React ecosystem, it requires time to master it. Because of this, many newbie developers configure their useEffect function in such a way that it causes an infinite loop problem. In this article, you will learn about the infamous infinite loop and how to solve it. Let’s get started!
React uses Object.is () to compare dependencies and if the values are changed, it will run a callback of useEffect. We can approach the update conditions in two cases. 1. Primitive Types When you put primitive values into the deps array, it will run whenever the value is changed. console.log ('Run when primitive value is changed.'); 2.
The issue is that upon each render cycle, markup
is redefined. React uses shallow object comparison to determine if a value updated or not. Each render cycle markup
has a different reference. You can use useCallback
to memoize the function though so the reference is stable. Do you have the react hook rules enabled for your linter? If you did then it would likely flag it, tell you why, and make this suggestion to resolve the reference issue.
const markup = useCallback(
(count) => {
const stringCountCorrection = count + 1;
return (
// Some markup that references the sections prop
);
},
[count, /* and any other dependencies the react linter suggests */]
);
// No infinite looping, markup reference is stable/memoized
useEffect(() => {
if (sections.length) {
const sectionsWithMarkup = sections.map((section, index)=> markup(index));
setSectionBlocks(blocks => [...blocks, ...sectionsWithMarkup]);
} else {
setSectionBlocks(blocks => []);
}
}, [sections, markup]);
Why is an infinite loop created when I pass a function expression
The "infinite loop" is the component re-rendering over and over because the markup
function is a NEW function reference (pointer in memory) each time the component renders and useEffect
triggers the re-render because it's a dependency.
The solution is as @drew-reese pointed out, use the useCallback
hook to define your markup
function.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With