бесконечное l oop при запросе API в редукционном действии - PullRequest
5 голосов
/ 19 марта 2020

Я пытаюсь запросить мой бэкэнд Firebase с помощью действия избыточного толчка, однако, когда я делаю это при моем первоначальном рендеринге с использованием useEffect(), я получаю эту ошибку:

Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.

Мой action просто возвращает снимок запроса Firebase, который я затем получил в своем редукторе. Я использую ловушку для отправки моего действия:

export const useAnswersState = () => {
    return {
        answers: useSelector(state => selectAnswers(state)),
        isAnswersLoading: useSelector(state => selectAnswersLoading(state))
    }
}

export const useAnswersDispatch = () => {
    const dispatch = useDispatch()
    return {
        // getAnswersData is a redux-thunk action that returns a firebase snapshot
        setAnswers: questionID => dispatch(getAnswersData(questionID))
    }
}

и следующие селекторы для получения данных, которые мне нужны из моих снимков и избыточных состояний:

export const selectAnswers = state => {
    const { snapshot } = state.root.answers
    if (snapshot === null) return []
    let answers = []
    snapshot.docs.map(doc => {
        answers.push(doc.data())
    })
    return answers
}

export const selectAnswersLoading = state => {
    return state.root.answers.queryLoading || state.root.answers.snapshot === null
}

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

const params = useParams() // params.id is just an ID string

const { setAnswers, isAnswersLoading } = useAnswersDispatch()
const { answers } = useAnswersState()

useEffect(() => {
    setAnswers(params.id)
}, [])

if (!isAnswersLoading)) console.log(answers)

Итак, чтобы уточнить, я использую мой useAnswersDispatch для отправки Действие Redux-Thunk, которое возвращает моментальный снимок данных Firebase. Затем я использую свой useAnswersState хук для доступа к данным после их загрузки. Я пытаюсь отправить свой запрос в useEffect моего фактического компонента представления, а затем отобразить данные с помощью моего обработчика состояния.

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

Ответы [ 2 ]

0 голосов
/ 26 марта 2020

Как прокомментировано; Я думаю, что ваш реальный код, который бесконечные циклы, зависит от setAnswers. В вашем вопросе вы забыли добавить эту зависимость, но код ниже показывает, как вы можете предотвратить изменение setAnswers и вызвать бесконечное l oop:

const GOT_DATA = 'GOT_DATA';
const reducer = (state, action) => {
  const { type, payload } = action;
  console.log('in reducer', type, payload);
  if (type === GOT_DATA) {
    return { ...state, data: payload };
  }
  return state;
};

//I guess you imported this and this won't change so
//   useCallback doesn't see it as a dependency
const getAnswersData = id => ({
  type: GOT_DATA,
  payload: id,
});

const useAnswersDispatch = dispatch => {
  // const dispatch = useDispatch(); //react-redux useDispatch will never change
  //never re create setAnswers because it causes the
  //  effect to run again since it is a dependency of your effect
  const setAnswers = React.useCallback(
    questionID => dispatch(getAnswersData(questionID)),
    //your linter may complain because it doesn't know
    //  useDispatch always returns the same dispatch function
    [dispatch]
  );
  return {
    setAnswers,
  };
};

const Data = ({ id }) => {
  //fake redux
  const [state, dispatch] = React.useReducer(reducer, {
    data: [],
  });

  const { setAnswers } = useAnswersDispatch(dispatch);
  React.useEffect(() => {
    setAnswers(id);
  }, [id, setAnswers]);
  return {JSON.stringify(state.data)}
; }; const App = () => {const [id, setId] = React.useState (88); return (
setId (id => id + 1)}> увеличить id
); }; ReactDOM.render ( , document.getElementById ('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Вот ваш исходный код, вызывающий бесконечное l oop, потому что setAnswers постоянно меняется.

const GOT_DATA = 'GOT_DATA';
const reducer = (state, action) => {
  const { type, payload } = action;
  console.log('in reducer', type, payload);
  if (type === GOT_DATA) {
    return { ...state, data: payload };
  }
  return state;
};

//I guess you imported this and this won't change so
//   useCallback doesn't see it as a dependency
const getAnswersData = id => ({
  type: GOT_DATA,
  payload: id,
});

const useAnswersDispatch = dispatch => {
  return {
    //re creating setAnswers, calling this will cause
    //  state.data to be set causing Data to re render
    //  and because setAnser has changed it'll cause the
    //  effect to re run and setAnswers to be called ...
    setAnswers: questionID =>
      dispatch(getAnswersData(questionID)),
  };
};
let timesRedered = 0;
const Data = ({ id }) => {
  //fake redux
  const [state, dispatch] = React.useReducer(reducer, {
    data: [],
  });
  //securit to prevent infinite loop
  timesRedered++;
  if (timesRedered > 20) {
    throw new Error('infinite loop');
  }
  const { setAnswers } = useAnswersDispatch(dispatch);
  React.useEffect(() => {
    setAnswers(id);
  }, [id, setAnswers]);
  return {JSON.stringify(state.data)}
; }; const App = () => {const [id, setId] = React.useState (88); return (
setId (id => id + 1)}> увеличить id
); }; ReactDOM.render ( , document.getElementById ('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
0 голосов
/ 19 марта 2020

Попробуйте изменить рефакторинг вашего создателя действия, чтобы в эффекте вызывался dispatch. Вы должны сделать отправку в зависимости от эффекта стрельбы.

См. Связанный

const setAnswers = (params.id) => {
  const dispatch = useDispatch();
  useEffect(() => {
    dispatch(useAnswersDispatch(params.id));
  }, [])
}

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

Я не уверен, откуда исходит params.id, но ваш компонент зависит от него, чтобы определить ответ из состояния приложения.

После запуска отправки обновляется только состояние приложения, но не состояние компонента. Устанавливая переменную с помощью useDispatch, у вас есть ссылка на переменную функции вашего хранилища резервов в жизненном цикле компонента.

Чтобы ответить на ваш вопрос, если вы хотите, чтобы он обрабатывал несколько отправок, добавьте params.id и dispatch в массив зависимостей вашего эффекта.

// Handle null or undefined param.id
const answers = (param.id) => getAnswersData(param.id);
const dispatch = useDispatch();
useEffect(() => {
     if(params.id) 
        dispatch(useAnswersDispatch(params.id));
  }, [params.id, dispatch]);

console.log(answers);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...