You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I recently had to fix a bug that made it to Production at work.
Uncaught Error: Maximum update depth exceeded. This can happen when a component repeatedly
calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of
nested updates to prevent infinite loops.
The error itself is a little misleading. Our app doesn't use either lifecycle method anywhere in the app.
After debugging the app in my local dev environment I was led to a file that had a handful of useEffects.
I hone in on useEffects whenever I'm chasing down infinite loops.
// actual code and logic removed for the sake of brevitylet[fooBar,setFooBar]=useState({foo: null,bar: null})useEffect(()=>{if(baz){setFooBar({ ...fooBar,bar: 123})}},[baz,fooBar])
Can you spot the infinite loop?
The Trap Explained 💣
When reading a useEffect I recommend reading the dependency array first.
If the value of baz or fooBar changes, the callback inside the useEffect will execute
Let's focus on the setFooBar function. We are updating the state with an object literal, spreading the previous state, and updating the bar property value to 123.
The previous state is fooBar. In the useEffect dependency array we see fooBar listed as a dependency.
If the value of fooBar changes, execute the useEffect callback. Set fooBar to {...fooBar, bar: 123}. This changes the value of fooBar so the useEffect callback will execute again.
There is an infinite loop.
Solution 1 - Dispatch function update 🙋
The ReactuseState hook returns an array of two items. The current state and a function that updates it.
Unbeknownst to many, this function accepts a callback function providing you access to the previous state.
This "previous state" is guaranteed to be the latest state, unlike relying on a closure. Kent C. Dodds has a great article explaining the useState dispatch function update here.
Let's update our useEffect code to utilize the function update instead.
Our useEffect has no dependency on the state we are updating which also solves our infinite loop issue.
Conclusion 🧑🏫
A safe useState rule to follow when updating state based on the previous state, is to always use the function update pattern.
Keep an eye out for this type of bug when reviewing code that contains a useEffect.
Thanks for reading 💙
The text was updated successfully, but these errors were encountered:
date: '2020-08-04'
category: react
image: /assets/venus-fly-trap.jpg
tags: [react, hooks, useeffect, javascript, js]
slug: set-state-trap
featured: yes
The Bug Hunt 🐛 🏹
I recently had to fix a bug that made it to Production at work.
The error itself is a little misleading. Our app doesn't use either lifecycle method anywhere in the app.
After debugging the app in my local dev environment I was led to a file that had a handful of
useEffect
s.I hone in on
useEffect
s whenever I'm chasing down infinite loops.Can you spot the infinite loop?
The Trap Explained 💣
When reading a
useEffect
I recommend reading the dependency array first.Let's focus on the
setFooBar
function. We are updating the state with an object literal, spreading the previous state, and updating thebar
property value to123
.The previous state is
fooBar
. In theuseEffect
dependency array we seefooBar
listed as a dependency.If the value of
fooBar
changes, execute theuseEffect
callback. SetfooBar
to{...fooBar, bar: 123}
. This changes the value offooBar
so theuseEffect
callback will execute again.There is an infinite loop.
Solution 1 - Dispatch function update 🙋
The React
useState
hook returns an array of two items. The currentstate
and a function that updates it.Unbeknownst to many, this function accepts a callback function providing you access to the previous
state
.This "previous state" is guaranteed to be the latest state, unlike relying on a closure. Kent C. Dodds has a great article explaining the
useState
dispatch function update here.Let's update our
useEffect
code to utilize the function update instead.Now, the
useEffect
no longer depends on thefooBar
value change to execute, solving our infinite loop issue!Solution 2 - useReducer 🧙♂️
I don't want
useReducer
to hijack the main topic of this blog post but it is a viable solution.Our
useEffect
has no dependency on thestate
we are updating which also solves our infinite loop issue.Conclusion 🧑🏫
A safe
useState
rule to follow when updating state based on the previous state, is to always use the function update pattern.Keep an eye out for this type of bug when reviewing code that contains a
useEffect
.Thanks for reading 💙
The text was updated successfully, but these errors were encountered: