Catarina Nogueira
Catarina Nogueira

Reputation: 1138

useSelector and UseEffect with dispatch functions create loop due to not updating state

I am trying to develop a React application that shows a list of items using Hooks but an infinite loop is happening. I think the state is not being identified as updated but I cannot tell why.

I have seen at the documentation that it could be the dependencies that I should add on the useEffect function, but when I remove the dependencies on useEffect it solves the problem. Moreover this is not what the documentation recommends to do, they recommend to put all dependencies on the list.

The codesandbox is here: https://codesandbox.io/s/react-listing-forked-6sd99 and if I uncomment the line 30 at Dashboard.ts it is possible to see the infinite loop, this is the line that calls the dispatch function (and also codesandbox will freeze for some seconds).

The useSelector and useEffect functions are as below, located at the Dashboard.tsx file:

  const catalog = useSelector<RootState, MetricState>(
    (state) => state.fetchMetrics
  );
  console.log(catalog);

  const dispatch = useDispatch();

  const classes = useStyles();
  useEffect(() => {
    // dispatch(appReducer.actionCreators.fetchMetrics());
  },[catalog, dispatch]));

The mocked data is as below, on the apiCreators.js file

export function fetchMetrics() {
  const action = {
    type: ACTION_TYPES.FETCH_METRICS,
    payload: []
  };
  return fetchMetricsCall(action);

const fetchMetricsCall = (action) => async (dispatch) => {
  try {
    dispatch({
      type: action.type,
      payload: {
        metrics: [
          {
            name: "one",
            owner: {
              team: "test"
            }
          },
          {
            name: "one",
            owner: {
              team: "test"
            }
          }
        ] //contains the data to be passed to reducer
      }.metrics
    });
  } catch (e) {
    dispatch({
      type: ACTION_TYPES.FAILURE,
      payload: console.log(e) //TODO: fix return type
    });
  }
};

Upvotes: 2

Views: 2330

Answers (1)

Michael Hoobler
Michael Hoobler

Reputation: 622

The main issue is the catalog inside [catalog, dispatch] found here:

useEffect(() => {
    // dispatch(appReducer.actionCreators.fetchMetrics());
  },[catalog, dispatch]));

The issue is that dispatch(appReducer.actionCreators.fetchMetrics()) is causing state.fetchMetrics (aka catalog) to update (which is an object), and javascript is "weird" when it comes to comparing objects/arrays.

So let's look at this:

let x = {a: 0}
let y = {a: 0}
let z = x

console.log(x === y) // this is FALSE
console.log(x === x) // this is TRUE
console.log(z === x) // this is TRUE

This article does a better job of going into it: http://adripofjavascript.com/blog/drips/object-equality-in-javascript.html

useEffect is doing something like if(oldState.catalog !== newState.catalog) {...rerender component} whenever there's a change in state (I feel like this might be really inaccurate, but I hope it helps kind of illustrate what's going on). So the component rerenders and makes a new call to dispatch, which updates the state and since one of the dependancies inside useEffect is an object, it's impossible to make an update that will tell useEffect to "stop rerendering".

There are ways to work around this, but you shouldn't need the catalog inside the [catalog, dispatch]. Simply removing it seems to fix the issue?

Upvotes: 3

Related Questions