This post was originally posted on my personal blog.
In Reactjs, we encountered infinite loops or useless re-rendering a lot. It's something unavoidable, but we can be more attentive about it sometimes. One of the reasons that cause us this kind of problems is useEffect; I'm going to talk about one of the mistakes we can make with it and learn how to be aware of it.
The mistake 😮
Check this out:
const [state, setState] = React.useState(0);
React.useEffect(() => {
console.log("re-rendering");
}, [{ ...someData }])
Now if we do setState
multiple times, we're going to see this in console:
re-rendering
re-rendering
re-rendering
Wait; what? We just passed someData
as a dependency list; why it logs that? That's not even related to state
. Yes, it's not related, but pay more attention to the dependency list; We passed an inline object, which means it's always different from its previous version; we create it every time we cause a re-render to the component. Check this:
{ ...someData } === { ...someData } // false
{} === {} // false
[] === [] // false
// all are inline and have different references
useEffect
somehow cache the dependency list and check if it's equal to the next value. That's why inline non-primitive values ([]
, {}
, {...data}
, ...) are always different in this tool's eyes.
Symbol()
is an exception here, it's different every time we create it, but it's a primitive value.
For example, check this, I saw many developers that they check part of an array like this:
const [state, setState] = React.useState([1, 2, 3, 4, 5]);
React.useEffect(() => {
console.log("re-rendering");
}, [state.slice(0, 2)]);
/*
prevVal = state.slice(0, 2) // first render
***
nextVal = state.slice(0, 2) // second render
***
prevVal === nextVal // false
*/
The expected behaviour is checking 1 to 3 values, but it doesn't work like that because the slice
method will always return a new array (reference).
I hope you enjoyed this article. Don't forget to share and send reactions to my article. If you wanted to tell me something, tell me on Twitter or mention me anywhere else, You can even subscribe to my newsletter and follow me on Github. 👋🏻
Top comments (9)
I totally made that mistake :-)
I think
useEffect
could have an optional third parameter for passing an equality function, for something like ramdajs.com/docs/#equalsjust JSON.stringify the object or the array makes it work though. But if structures are too deep/large this can be a bad idea.
For JSON.stringify, wow, horrible idea, I can't even think about it. And yea, I hope you learned from that mistake.
oh yes, it is a horrible hack :-)
I found this very useful when I tried to understand
useEffect
better: twitter.com/dan_abramov/status/110...deep equality checks, i.e. structural equality is probably not a good idea in
useEffect
at all. There might be edge cases where it could make sense, but actually, it almost never does.Exactly.
Your article and this comment go into great depth what not to do, but as a react newbie I’m still not sure how to handle nested objects in state. Should I break them apart and manage each sub item of an object or array as individual variables?
Thanks, I appreciate that. The thing that I should say, is that don't worry. when it's too big, split it, or maybe use a good state-management library like jotai and zustand, or server-state-management library like react-query.
if you are just learning React, I can really recommend zustand as a first state manager, because I feel zustand is what unopinionated
useState
actually should have been in the first place.jotai (which is very similar to recoil) is great too. It provides a great solution to the state management problem, just in a different way. If you have an FP background, jotai will feel natural right away, if not going with zustand will be easier I suppose.
when I learned React, I went with plain
useState
, because Redux was too much for me. But as I progressed I always had the feeling there's something is missing but couldn't really tell what it was.And it has something to do with deeply nested state:
useState
doesn't deliver good answers for that, and then something like the JSON.stringify hack pops up. You know you shouldn't do it, but what else should you do?zustand provides a good solution for that. It allows you to define a possibly very deep object structure and a setter for deep partial updates. You do that outside of React with a
create
function that will provide you a hook to use the structure and the setter inside React components. This hook gives you the ability to [select slices of that state] as you need them.When you think about dependencies of
useEffect
and large, deeply nested object structures, ideally you would like to say "collect n primitive values from somewhere in the structure and return them as an array I can pass touseEffect
".And you exactly say that with the "state array picks" of zustand.
I hope this can help you a little on your React journey.
Great explanation!
Thanks to you both! I’ll check these out once I move beyond the simple apps I’m playing with now. I heard an interview with the guy behind React Query on Syntax.fm and it sounded like it solved a lot of these problems and more. To the google I go!