【问题标题】:setState called in render prop is causing a react warning在渲染道具中调用的 setState 导致反应警告
【发布时间】:2020-07-16 18:41:14
【问题描述】:

我有以下组件,它接受一个将值传递给子组件的渲染道具。 Here 是一个显示问题的代码框。按提交并查看控制台。

这是组件:

export const FormContainer = function FormContainer<V>({
  initialValues,
  validate,
  render,
  ...rest
}: FormContainerProps<V>) {
  const [hasValidationError, setHasValidationError] = useState(false);
  const dispatch = useDispatch();

  useEffect(() => {
    if (!hasValidationError) {
      return;
    }

    scrollToValidationError();

    () => setHasValidationError(false);
  }, [hasValidationError]);

  return (
    <>
      <Formik
      >
        {({
          isSubmitting,
          submitCount,
          isValid,
          errors,
          values,
        }: FormikProps<V>) => {
          const invalid = !isValid;
          const submitted = submitCount > 0;

          if (submitCount > 0 && invalid) {
            setHasValidationError(true);
          }

          return (
            <>
              <Form>
                  <div className={styles.form}>
                    {render({
                      values,
                      errors,
                      isSubmitting,
                      invalid,
                      submitCount,
                    })}
                  </div>
              </Form>
            </>
          );
        }}
      </Formik>
    </>
  );
};

如果存在验证错误,则调用 setHasValidationError,这会导致 react 出现此错误

Warning: Cannot update a component (`FormContainer`) while rendering a different component (`Formik`). To locate the bad setState() call inside `Formik`, follow the stack trace as described in 
    in Formik (created by FormContainer)
    in FormContainer (created by Home)
    in Home (created by Context.Consumer)
    in Route (created by App)
    in Switch (created by App)
    in Router (created by App)
    in App

我并不是说这个警告是错误的。在这里调用 setHasValidationError 似乎并不理想,但在初始 useEffect 钩子中调用的 scrollToValidationError(); 调用是异步的,它需要超出渲染函数。

我能做什么?

【问题讨论】:

  • 我认为 hackape 是对的,反正是个 eslint 对吗? :P

标签: reactjs


【解决方案1】:

为了避免 Formik 出现这个问题,您可以将状态调用包装在 setTimeouts 中,这样应该可以解决问题:

        setTimeout(() => setHasValidationError(true), 0);

这也是 Formik 在其官方文档中所做的。这是他们有一段时间的问题,诀窍是让状态更新在下一个周期滴答时运行。

另见:https://github.com/jaredpalmer/formik/issues/1218

【讨论】:

  • 我认为useEffect 而不是setTimeout 更好,因此您可以避免在卸载组件后调用setState 的不幸情况。我对source code 进行了安全检查,看起来还不错,虽然 eslint 天真地抱怨道。
【解决方案2】:

我认为阿里使用setTimeout 的回答是合法的。我想补充一点,useEffect 是 IMO 更好的解决方案。因为它进一步防止了在组件卸载后调用 setHasValidationError 时不太可能但仍然可能的错误情况。

(eslint 天真地抱怨 useEffect 在这里使用不安全,但我检查了 source code 完全没问题。)

// here I rename useEffect to mute eslint error
const nextTick = useEffect;

<Formik>
  {({
    isSubmitting,
    submitCount,
    isValid,
    errors,
    values
  }: FormikProps<V>) => {
    const invalid = !isValid;

    nextTick(() => {
      if (submitCount > 0 && invalid) {
        setHasValidationError(true);
      }
    }, [submitCount, invalid]);

    // ...
  }
</Formik>

【讨论】:

  • 提醒:React docs suggest 我们每次在一个组件中调用useEffect 的顺序都是一样的。这意味着useEffect 应该在任何条件返回之前出现,而不是在循环中运行,也不在回调中运行。 Formik 示例在&lt;Formik&gt; 的回调中使用useEffect。因此,setTimeout() 方法最有意义。
  • 你是对的。我确实考虑到了这一点,因此我检查了源代码。对于当前的实施,这很好。但是,是的,不能保证 Formik 会保持一致的内部实现,所以你的推理成立。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2023-03-19
  • 1970-01-01
  • 2022-01-22
  • 2021-10-31
  • 2018-11-16
  • 2019-02-03
  • 2017-03-11
相关资源
最近更新 更多