useEffect multiple dependencies - what is causing multiple renders?

Viewed 49

So in my code below, I want to understand what is causing the component to re-render multiple times. Even though most of the time this code runs without any problem, but sometimes it causes the browser to become unresponsive.

I can't pinpoint what is causing that problem and believe that relooking at the useEffect might be the key to solving it.

Because the dispatch, props & addToast are dependent on success, should I remove them from the dependency array and just keep success and error?

Is there a way to figure out which dependency to keep and which to remove?

useEffect(() => {
    if (success) {
      dispatch({ type: PRODUCT_RESET });
      dispatch(listProducts());
      props.onHide();
      addToast("Product has been added!", {
        appearance: "success",
        autoDismiss: false,
      });
    } else if (error) {
      addToast(error, { appearance: "error", autoDismiss: false });
    }
  }, [dispatch, success, error, addToast, props]);
1 Answers

Moving comment from @Ori Drori into answer.


[...] If the parent re-renders, the child will always receive it's props in a new object, even if nothing in it has changed. [...]

Source: "Does React make any guarantees that the props object reference stays stable?"

The problem you are observing is likely the cause of dispatch triggering the parent component to re-render which then creates a new props object and thus re-renders your component. This is an infinite feedback loop.

You can avoid this issue by removing props from the dependency list and instead add props.onHide. This alone may not be enough because the identity of props.onHide could change if it is defined as a plain function within the parent component.

You may have to use useCallback in the parent component to ensure that the identity of the function is stable, otherwise, you could encounter the same issue.


You can also remove dispatch from the list of dependencies, because react guarantees that it doesn't change between renders:

React guarantees that dispatch function identity is stable and won't change on re-renders. This is why it's safe to omit from the useEffect or useCallback dependency list.

Source: "Hooks API Reference"


To summarize, your dependency list would look like this:

useEffect(() => {
    // ...
}, [success, error, addToast, props.onHide]);

(There is also this mysterious listProducts function if that is defined within the component you may have to add it as dependency as well.)

And in the parent component you may have to use useCallback when you define the onHide function:

// The child component could cause this component to re-render creating a 
// feedback loop.  The 'useCallback' prevents the identity of 'onHide' from 
// changing thus breaking the loop.
let onHide = useCallback(() => {
    // ...
}, [/* ... */]);

// ...

<Foo onHide={onHide} />

Since that apparently helped, I am moving this from a comment into the answer:

This article briefly touches the whole useCallback thing, that helped me understand it.

Related