useSelector и useEffect с функциями отправки создают цикл из-за отсутствия обновления состояния

#reactjs #react-redux #react-hooks #infinite-loop #dispatch

Вопрос:

Я пытаюсь разработать приложение React, которое показывает список элементов с помощью крючков, но происходит бесконечный цикл. Я думаю, что состояние не идентифицируется как обновленное, но я не могу сказать, почему.

Я видел в документации, что это могут быть зависимости, которые я должен добавить в функцию useEffect, но когда я удаляю зависимости от useEffect, это решает проблему. Более того, это не то, что рекомендует делать документация, они рекомендуют поместить все зависимости в список.

Кодовое поле находится здесь: https://codesandbox.io/s/react-listing-forked-6sd99 и если я раскомментирую строку 30, Dashboard.ts то можно увидеть бесконечный цикл, это строка, которая вызывает dispatch функцию (а также codesandbox замерзнет на несколько секунд).

Функции useSelector и useEffect, как показано ниже, расположены в Dashboard.tsx файле:

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

  const dispatch = useDispatch();

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

Издевательские данные, как показано ниже, в apiCreators.js файле

 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
    });
  }
};
 

Комментарии:

1. Я не могу воспроизвести вашу ошибку, я раскомментирую строку, и ошибка не возникает

2. Извините, чего-то не хватало, обновляю код!

Ответ №1:

Главная проблема заключается catalog в том, что внутри [catalog, dispatch] можно найти здесь:

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

Проблема в том, что dispatch(appReducer.actionCreators.fetchMetrics()) это вызывает state.fetchMetrics (aka catalog) обновление (которое является объектом), и javascript «странный», когда дело доходит до сравнения объектов/массивов.

Итак, давайте посмотрим на это:

 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
 

Эта статья лучше справляется с этим: http://adripofjavascript.com/blog/drips/object-equality-in-javascript.html

useEffect делает что-то вроде if(oldState.catalog !== newState.catalog) {...rerender component} всякий раз, когда происходит изменение состояния (я чувствую, что это может быть действительно неточно, но я надеюсь, что это поможет проиллюстрировать, что происходит). Таким образом , компонент повторно отправляет и выполняет новый вызов dispatch , который обновляет состояние, и поскольку одна из зависимостей внутри useEffect является объектом, невозможно выполнить обновление, которое сообщит useEffect «прекратить повторную передачу».

Есть способы обойти это, но вам не должна понадобиться catalog внутренняя [catalog, dispatch] часть . Простое удаление, по-видимому, устраняет проблему?

Комментарии:

1. да, удаление каталога решило проблему, но мне было интересно узнать, почему, теперь я понял! Спасибо вам за ваши пояснения и ссылки! 🙂