Sbagaria2710
Sbagaria2710

Reputation: 97

useEffect causing infinite looping with constant dependency

useEffect is getting triggered even though the dependency array has constant object.

I tried extracting the logic and put the object in useState

const payload = {
    limit: 5,
    offset: 0,
    filterBy: 'All',
};

useEffect(() => {
    const defaultPayload = {
        limit: 10,
        offset: 0,
        filterBy: 'All',
    };
        dispatch({ type: RANDOM_CONST, payload: payload || defaultPayload });
}, [dispatch, payload]);

It should trigger only when payload changes. Since, payload is a constant, it should just run once and note infinite times.

Upvotes: 1

Views: 1769

Answers (2)

Manu Cammaert
Manu Cammaert

Reputation: 26

You can convert the objects to JSON strings and compare them inside your useEffect hook:

JSON.stringify(oldValue) === JSON.stringify(newValue) // true

Note that object keys should have the same order:

JSON.stringify({a: 1, b: 2}) === JSON.stringify({a: 1, b: 2}) // true
JSON.stringify({a: 1, b: 2}) === JSON.stringify({b: 2, a: 1}) // false

Upvotes: 0

Dennis Vash
Dennis Vash

Reputation: 53964

There are two problems that may cause the re-render, notice that the useEffect makes a shallow comparison with the dep-array values:

  1. payload is a new object on every render, therefore oldPayload === payload always false, causes the useEffect to run.

  2. If dispatch comes from 3rd party library (like react-redux hook), it might be making a new dispatch reference on every render, therefore again oldDispatch === dispatch is aways false and causes useEffect to run.

To fix it, you can move the "constant object" to the outer scope (which will run once), and use useCallback hook if you passing the dispatch.

Example from react-redux docs: When passing a callback using dispatch to a child component, it is recommended to memoize it with useCallback, since otherwise child components may render unnecessarily due to the changed reference.

const payload = {
  limit: 5,
  offset: 0,
  filterBy: 'All'
};

const App = () => {
  //                   v Memoize it if passing as a callback,
  //                     check in library docs if there is a new instance
  //                     on every render
  const dispatch = useDispatch();

  useEffect(() => {
    dispatch({ type: RANDOM_CONST, payload });
  }, []);

  return;
};

Note: There is no indication in question where the dispatch function comes from.

Upvotes: 8

Related Questions