反应形式验证钩导致无限的重新渲染循环

时间:2020-04-13 16:05:56

标签: reactjs typescript react-hooks eslint

我已经检查了其他问题,他们没有帮助我

我构建了一个React钩子来验证表单输入。但是,这会导致无限的重新渲染循环。我已将问题归结为useEffect依赖项数组。当我排除validators依赖项时,它的效果很好!我永远不会在运行时更改validators,所以在依赖项数组中不需要此道具吗?我的ESLint react-hooks-plugin一直警告我validators依赖项丢失。请帮帮我。如果我在运行时不进行更改,是否可以从validators依赖项数组中删除useEffect依赖项?这是我的钩子和表单组件:

import { useState, useEffect } from "react";

function setObjectValues<
  K extends { [key: string]: unknown },
  T
>(
  object: K,
  value: T
): { [key in keyof K]?: T } {
  const initialResults: {
    [key in keyof K]?: T;
  } = {};
  for (const key in object) {
    initialResults[key] = value;
  }
  return initialResults;
}

export function useValidation<
  K,
  T extends {
    [key: string]: (value: K) => boolean;
  }
>(
  value: K,
  validators: T
): {
  valid: boolean;
  results: { [key in keyof T]?: boolean };
} {
  const [results, setResults] = useState<
    { [key in keyof T]?: boolean }
  >(setObjectValues(validators, true));

  useEffect(() => {
    const newResults: {
      [key in keyof T]?: boolean;
    } = {};

    for (const key in validators) {
      const valid = validators[key](value);
      newResults[key] = valid;
    }

    setResults(newResults);
  }, [value, validators]);

  const valid = Object.values(results).every(
    (item) => item === true
  );

  return { valid, results };
}

我的组件

import { NextPage } from "next";
import {
  useFirebase,
  useValidation,
} from "app/hooks";
import {
  useState,
  useCallback,
  FormEvent,
} from "react";
import { useRouter } from "next/router";

type InputType = "email" | "password";

const SignUp: NextPage = () => {
  const firebase = useFirebase();
  const router = useRouter();

  const [email, setEmail] = useState("");
  const {
    valid: emailValid,
    results: emailValidationResults,
  } = useValidation(email, {
    containsAt: (value) => value.includes("@"),
  });

  const [password, setPassword] = useState("");
  const {
    valid: passwordValid,
    results: passwordValidationResults,
  } = useValidation(password, {
    isLongEnough: (value) => value.length >= 8,
    containsLowerCase: (value) =>
      value.toUpperCase() !== value,
    containsUpperCase: (value) =>
      value.toLowerCase() !== value,
    containsNumber: (value) => /\d/.test(value),
  });

  const handleSubmit = useCallback(
    (event: FormEvent<HTMLFormElement>) => {
      event.preventDefault();
      if (
        emailValid === true &&
        passwordValid === true &&
        email !== "" &&
        password !== ""
      ) {
        const error = firebase.createUser(
          email,
          password
        );
        if (error) {
          console.warn(error.code);
        } else {
          router.push("/");
        }
      } else {
        console.warn("Invalid user values");
      }
    },
    [
      email,
      emailValid,
      firebase,
      password,
      passwordValid,
      router,
    ]
  );

  console.log(emailValid, passwordValid);

  return (
    <form onSubmit={handleSubmit}>
      <label htmlFor="email">Email</label>
      <input
        value={email}
        onChange={(event): void =>
          setEmail(event.target.value)
        }
        id="email"
        placeholder="Email"
      />
      <p>{emailValid}</p>
      <label htmlFor="password">Password</label>
      <input
        value={password}
        onChange={(event): void =>
          setPassword(event.target.value)
        }
        id="password"
        placeholder="Password"
      />
      <p>{passwordValid}</p>
      <button type="submit">Submit</button>
    </form>
  );
};

export default SignUp;

1 个答案:

答案 0 :(得分:2)

const {
    valid: passwordValid,
    results: passwordValidationResults,
  } = useValidation(password, {
    isLongEnough: (value) => value.length >= 8,
    containsLowerCase: (value) =>
      value.toUpperCase() !== value,
    containsUpperCase: (value) =>
      value.toLowerCase() !== value,
    containsNumber: (value) => /\d/.test(value),
  });

在这里,validators实际上是在Signup渲染期间创建的对象。创建新对象时,尽管其中的值可能相同,但它始终是一个不同的新对象。这就是为什么将其添加到依赖关系数组会导致无限重新渲染的原因。

如果对象不依赖于组件的状态或道具,请将声明移到组件外部,以便仅创建一次。

// outside SignUp
const validators = {
    isLongEnough: (value) => value.length >= 8,
    containsLowerCase: (value) =>
      value.toUpperCase() !== value,
    containsUpperCase: (value) =>
      value.toLowerCase() !== value,
    containsNumber: (value) => /\d/.test(value),
  };

// inside SignUp
const {
    valid: passwordValid,
    results: passwordValidationResults,
  } = useValidation(password, validators);

我建议将validators保留在依赖项数组中,因为它仍然可以正常工作。大多数情况下将其遗漏是一种代码异味。在大多数情况下,如果validators发生了变化,您将希望再次重新运行效果。