DEV Community

loading...
Cover image for Big useEffect mistake

Big useEffect mistake

aslemammad profile image M. Bagher Abiat ・2 min read

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 }])
Enter fullscreen mode Exit fullscreen mode

Now if we do setStatemultiple times, we're going to see this in console:

re-rendering
re-rendering
re-rendering
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
*/
Enter fullscreen mode Exit fullscreen mode

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. 👋🏻

Discussion (9)

pic
Editor guide
Collapse
nibtime profile image
nibtime

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/#equals

just JSON.stringify the object or the array makes it work though. But if structures are too deep/large this can be a bad idea.

Collapse
aslemammad profile image
M. Bagher Abiat Author

For JSON.stringify, wow, horrible idea, I can't even think about it. And yea, I hope you learned from that mistake.

Collapse
benhammondmusic profile image
Ben Hammond

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?

Thread Thread
aslemammad profile image
M. Bagher Abiat Author

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.

Thread Thread
nibtime profile image
nibtime

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 to useEffect".

And you exactly say that with the "state array picks" of zustand.

I hope this can help you a little on your React journey.

Thread Thread
aslemammad profile image
M. Bagher Abiat Author

Great explanation!

Thread Thread
benhammondmusic profile image
Ben Hammond

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!

Collapse
nibtime profile image
nibtime • Edited

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.

Thread Thread
aslemammad profile image