React Infinite Loop Error Causes Useful Article - mosinn/DOCS-n-Snippets-n-Steps GitHub Wiki
typeofnan.dev/fix-the-maximum-update-depth-exceeded-error-in-react/
React is an excellent framework, but it can have some tricky “gotchas.” One of which is when you accidentally cause an infinite render loop, often resulting in the cryptic “maximum update depth exceeded” error.
You likely have gotten yourself into an infinite render loop. Here I’ll discuss the most frequent causes and how to fix them.
You can get yourself into an infinite render loop by accidentally calling a state setter function rather than passing a reference to it. Consider the following code:
function App() {
const [terms, setTerms] = useState(false);
const acceptTerms = () => {
setTerms(true);
};
return (
<>
<label>
<input type="checkbox" onChange={acceptTerms()} /> Accept the Terms
</label>
</>
);
}
function App() {
const [terms, setTerms] = useState(false);
const acceptTerms = () => {
setTerms(true);
};
return (
<>
<label>
<input type="checkbox" onChange={acceptTerms} /> Accept the Terms
</label>
</>
);
}
A nice feature of React is that it’s reactive. We can use the useEffect hook to take an action based on a variable being updated. However, this can backfire: if the useEffect hook updates a variable that triggers the effect to re-run, then it’s going to keep updating and re-running, causing an infinite loop. Let’s consider the following example:
function App() {
const [views, setViews] = useState(0);
useEffect(() => {
setViews(views + 1);
}, [views]);
return <>Some content</>;
}
One way to prevent this from happening is to use a callback function in your state setter:
setViews((v) => v + 1);
This will allow you to safely remove the views variable from the dependency array.
function App() {
const [views, setViews] = useState(0);
useEffect(() => {
setViews((v) => v + 1);
}, []);
return <>Some content</>;
}
There might be some reason you can’t clean up the dependency array. It’s not optimal, but you could add an additional variable to the mix that limits whether the effect is re-run. For example, you could create an isInitialRender stateful variable.
function App() {
const [views, setViews] = useState(0);
const [isInitialRender, setIsInitialRender] = useState(true);
useEffect(() => {
if (isInitialRender) {
setIsInitialRender(false);
setViews(v + 1);
}
}, [views, isInitialRender]);
return <>Some content</>;
}
The useEffect hook uses referential equality to determine if any variables changed in its dependency array. This means that, even if an object appears identical to another object, the effect could be re-triggered because the objects are actually different in memory.
A good example of this is when an effect depends on a function that’s declared within the component itself. For example:
function App() {
const [views, setViews] = useState(0);
const incrementViews = () => {
setViews((v) => v + 1);
};
useEffect(() => {
incrementViews();
}, [incrementViews]);
return <>Some content</>;
}
React has a couple solutions for this issue.
One quick solution is to move the function inside the useEffect hook.
function App() {
const [views, setViews] = useState(0);
useEffect(() => {
const incrementViews = () => {
setViews((v) => v + 1);
};
incrementViews();
}, []);
return <>Some content</>;
}
The useCallback hook works a lot like the useEffect hook where the first argument is a function and the second argument is a dependency array. The difference is the useCallback hook will not create a new function unless one of the variables in its dependency array changes.
We can see the useCallback hook in action here:
function App() {
const [views, setViews] = useState(0);
const incrementViews = useCallback(() => {
setViews((v) => v + 1);
}, []);
useEffect(() => {
incrementViews();
}, [incrementViews]);
return <>Some content</>;
}
Conclusion React is awesome because it reacts to changes in state really well. Sometimes too well if you’re not careful! Hopefully these tips have solved any issues you’re currently hitting.