question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Discussion: useEffect hook with array dependency that has a default value causes render loop

See original GitHub issue

So maybe this is not a bug (because it’s default JavaScript behavior) but a pitfall that should be documented in the useEffect-section of the hooks documentation. I’m not quite sure because it feels like react should handle this as default value assignment on destructuring functional component props is recommended.

When a useEffect-Hook that has an array-prop with a default value as one of its dependencies calls a function mutating any other prop, a render loop will occur.

React version: 16.12.0

Steps To Reproduce

  1. Create a functional component that has 3 props: – an array prop that has a default value (created within destructuring) – any other prop (e.g. string) – a callback function to mutate the second prop
  2. Have an useEffect function that calls the callback to mutate the second prop. The useEffect needs to have the array prop as one of its dependencies

Link to code example:

https://codesandbox.io/s/smoosh-field-fqduq

The current behavior

When the default value for the array prop is set to an “anonymous” array (or object) inside of the props destructuring, the useEffect will always trigger and so the second prop will be changed via the callback function and a re-render will happen. Then the effect will be triggered again as the value for the array will have changed again.

The expected behavior

Default values for arrays (and objects) can be assigned within the destructuring of the props without causing re-renders (or even render loops) when the mentioned useEffect is not present (like, why should it trigger a re-render?). I’d expect react to behave the same in this case (don’t compare empty arrays/objects by reference). If not, there should be at least a warning in the docs of useEffect that this is something to look out for.

Workaround

If the default prop is saved as a variable outside the functional component and then assigned as the default value (read: if you have referential equality), the issue does not occur obviously (default JavaScript behavior).

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Reactions:15
  • Comments:12 (1 by maintainers)

github_iconTop GitHub Comments

8reactions
eps1loncommented, Feb 27, 2020

To avoid this you can declare it outside e.g.

const defaultArrayProp = [];
function MyComponent({ arrayProp = defaultArrayProp, title, onEvent }) {  

Consumers of your component might still encounter this pitfall when using <MyComponent arrayProp={[]} /> if MyComponent triggers a re-render in its owner as well.

7reactions
gregor-muellercommented, Jul 6, 2020

Sure, this would be correct but also a bit too much hassle where just having a reference to an empty array would also help. Note: having an empty array or even object as default is something I repeatedly see being done wrongly as mentioned in the start of the topic. As beginners would run into this, I don’t think it is very beginner-friendly to have them having to work around this (e.g. using useMemo). I guess this would just lead to beginners always wrapping everything in useMemo, trying to fight unnecessary re-renders which will just cause memory issues in the long run.

@tejas-bontadka, @martinjaime is this what you’ve been thinking of?

function MyComponent({ arrayProp, title, onEvent }) {  
  
  // this fixes the render loop but seems a bit over engineered
  // as a simple reference to an empty array would also fix it
  const arrayPropToUse = useMemo(() => {
    if (!Array.isArray(arrayProp)) return [];
    return arrayProp;
  }, [arrayProp]);

  useEffect(() => {
    console.log("arrayPropToUse effect", arrayPropToUse);
    onEvent(title + "1"); // title prop gets updated triggering a re-render, but only once
  }, [arrayPropToUse]); 

  return (
    <div>
      <h1>{title}</h1>
      <p>Feels a bit over engineered</p>
    </div>
  );
}

My whole point is not that there are no solutions to this but that this is nothing a beginner would know or work around effectively (without developing potentially bad habits like using useMemo everywhere). But that’s just an assumption on my side (based on what I’ve seen), which should of course be discussed.

Read more comments on GitHub >

github_iconTop Results From Across the Web

How to fix missing dependency warning when using useEffect ...
I have this current setup, React hook useEffect runs continuously forever/infinite loop and the only comment is about useCallback() which I'm not familiar...
Read more >
Hooks and state 102: the Dependency array in useEffect()
One of the more confusing aspects of the useEffect() hook is the dependency array. As a reminder, here's the syntax for useEffect:
Read more >
The last guide to the useEffect Hook you'll ever need
In this case, “conditions” mean that one or more dependencies have changed since the last render cycle. Dependencies are array items provided as ......
Read more >
Preventing infinite re-renders when using useEffect and ...
Changing state will always cause a re-render. By default, useEffect always runs after render has run. This means if you don't include a...
Read more >
Understanding useEffect: the dependency array
By default, useEffect always runs after render has run. This means if you don't include a dependency array, and you're using useEffect to ......
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found