每次在useEffect块中获取setstate导致重新渲染

时间:2020-04-07 20:44:08

标签: reactjs

我已经创建了这个codesandbox示例,下面是代码:

import React, { ReactNode, useState } from "react";
import { Formik, FormikConfig, FormikProps, Form, FormikErrors } from "formik";
import { useEffect } from "react";
import { scrollToValidationError } from "./scrollToValidationError";

// const isEmpty = (a: unknown): boolean =>
//   typeof a === "object" && Object.keys(a).length > 0;

export type FormContainerProps<V> = {
  render({
    values,
    errors,
    invalid,
    submitCount,
    isSubmitting
  }: {
    values: V;
    invalid: boolean;
    errors: FormikErrors<V>;
    submitCount: number;
    isSubmitting: boolean;
  }): ReactNode;
  additionalContent?: ReactNode;
  nextButtonText?: string;
} & Pick<FormikConfig<V>, "initialValues" | "validate"> &
  Partial<Pick<FormikConfig<V>, "onSubmit">>;

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

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

    scrollToValidationError();
  }, [hasValidationError]);

  return (
    <>
      <Formik
        initialValues={initialValues}
        validate={validate}
        onSubmit={async (values, { validateForm }) => {}}
      >
        {({
          isSubmitting,
          submitCount,
          isValid,
          errors,
          values
        }: FormikProps<V>) => {
          const invalid = !isValid;

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

          return (
            <>
              <div data-selector="validation-summary">Validation Summary</div>
              <Form>
                <div>
                  <div>
                    {render({
                      values,
                      errors,
                      isSubmitting,
                      invalid,
                      submitCount
                    })}
                  </div>
                  <div>
                    <button type="submit">SUBMIT</button>
                  </div>
                </div>
              </Form>
            </>
          );
        }}
      </Formik>
    </>
  );
};

基本上,我正在呼叫setHasValidationError(true),这打破了useEffect上的依赖项监视程序

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

    scrollToValidationError();

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

但是,如果这是一个有多个错误的表单,那么我想每次触发useEffect,但是我不知道何时将其重置为false或是否有更好的方法。

4 个答案:

答案 0 :(得分:2)

要在单击“提交”后滚动到第一个错误字段,可以执行以下操作:

  • 编写一个自定义组件(例如FocuseabelField),该组件呈现一个formik字段,该字段也处理automatic scroll到元素和错误输入时的focus
  • 使用Formik's innerRef
  • 只需使用formik的isSubmittingerrors来处理滚动和聚焦逻辑

FocuseabelField自定义组件

const FocuseabelField: any = props => {
  const elementRef = useRef<HTMLDivElement>();
  if (
    props.isSubmitting &&
    elementRef.current !== undefined &&
    props.errors.hasOwnProperty(props.name)
  ) {
    elementRef.current.scrollIntoView();
    elementRef.current.focus();
  }
  return <Field {...props} innerRef={elementRef} />;
};

用法

<FocuseabelField
  errors={errors}
  isSubmitting={isSubmitting}
  name="name"
  placeholder="enter name"
  className={errors && errors.name ? "input error" : "input"}
/>
  • 我接受了您的代码,并注释了诸如scrollToValidationerror.ts,dom.ts,wait.ts,useState(hasValidationError),useEffect等之类的东西。

  • 此处是代码的简化工作副本。我使用2个字段来演示多个错误,并自动滚动并聚焦到错误字段:

https://codesandbox.io/s/usemachine-typescript-problems-tns0c?file=/src/components/Home/index.tsx

当表单变大时,管理变得复杂,因此考虑将表单验证部分外包并使用诸如yup之类的库并维护验证模式并将其传递给formik很好。

看看formik docs为例。

答案 1 :(得分:1)

如何为每个表单字段创建带有键的对象?这样,您可以为每个输入维护一个特定的表单验证错误,并在useEffect第二个参数中使用该对象,这将确保它在每次表单错误更新时被触发

答案 2 :(得分:0)

要回答您的原始问题,

useEffect会参考检查其依赖关系,因此您可以使用Object代替值。像这样。

const [hasValidationError, setHasValidationError] = useState({value: false});

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

    scrollToValidationError();
  }, [hasValidationError]);
setHasValidationError({value: true});

但是关于Formik的使用,我强烈建议您遵循@gdh指出的内容。

答案 3 :(得分:0)

我不明白这里是否需要效果。

为什么不直接使用方法而不使用钩子呢?

这样做可以避免两次重新渲染,并且组件也可以是无状态的!

...

  }: FormikProps<V>) => {
    const invalid = !isValid;

    if (submitCount > 0 && invalid) {
      scrollToValidationError();
    }
          
...