在redux操作中查询api时发生无限循环

时间:2020-03-19 01:56:06

标签: javascript reactjs firebase redux react-redux

我试图通过redux-thunk操作查询我的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.

我的操作只是返回一个Firebase查询快照,然后在我的reducer中收到它。我使用钩子来调度我的动作:

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

和以下选择器从快照和redux状态获取所需的数据:

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的值时,出现了上面的错误。我将不胜感激任何帮助,并乐于提供任何更多信息(如果有帮助的话),但是,我已经测试了我的reducer和action本身,两者均按预期工作,因此我相信问题出在文件中如上所述。

3 个答案:

答案 0 :(得分:0)

尝试重构动作创建者,以便在效果中调用dispatch。您需要根据效果触发进行调度。

See related

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

假设getAnswersData是一个选择器,效果将触发调度到您的应用程序状态,当您返回响应时,选择器getAnswersData将选择所需的字段。

我不确定params.id的来源,但是您的组件依靠它来确定应用程序状态的答案。

触发调度后,仅更新应用程序状态,而不更新组件状态。使用useDispatch设置变量,您可以在组件的生命周期中对Redux存储的调度功能进行变量引用。

要回答您的问题,如果您希望它处理多个调度,请将params.iddispatch添加到效果中的依赖项数组中。

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

答案 1 :(得分:0)

如所评论;我认为您无限循环的实际代码依赖于setAnswers。在您的问题中,您忘记添加此依赖关系,但是下面的代码显示了如何防止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 => {
  // 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 <pre>{JSON.stringify(state.data)}</pre>;
};
const App = () => {
  const [id, setId] = React.useState(88);
  return (
    <div>
      <button onClick={() => setId(id => id + 1)}>
        increase id
      </button>
      <Data id={id} />
    </div>
  );
};

ReactDOM.render(<App />, 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>

这是您的原始代码,导致无限循环,因为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 <pre>{JSON.stringify(state.data)}</pre>;
};
const App = () => {
  const [id, setId] = React.useState(88);
  return (
    <div>
      <button onClick={() => setId(id => id + 1)}>
        increase id
      </button>
      <Data id={id} />
    </div>
  );
};

ReactDOM.render(<App />, 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>

答案 2 :(得分:0)

您只需要添加 params.id 作为依赖项。