在渲染props中调用的 setState 导致react警告

IT技术 reactjs
2021-05-18 06:17:58

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

这是组件:

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在这里调用似乎并不理想,但是scrollToValidationError();在初始useEffect钩子中调用的调用是异步的,它需要在渲染函数之外。

我能做什么?

2个回答

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

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

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

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

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

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

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