如何防止其他子组件不必要的重新渲染?

问题描述 投票:0回答:1

我正在为我的应用程序构建一个简单的登录页面,其结构如下所示:

<SignIn />
  --> <SignInFormComponent />
  --> <NeedHelpComponent />

SigInFormComponent 有 2 个输入框。 1 用于电子邮件,1 用于密码和“继续”按钮。 NeedHelpComponent 是一个用于重置密码的可折叠链接。

因此,如果我点击“继续”提交登录信息,则只有其父组件 SignIn 和 SignInFormComponent 会重新渲染,这很好,因为我想在 SignIn 组件中显示登录错误消息。

但是,如果我在 NeedHelpComponent 中隐藏/显示密码重置链接,则所有 3 个组件都会重新呈现。所以我的问题是如何防止 SignIn 和 SignInFormComponent 重新渲染?

export default function SignIn() {
  const [loginErrMessage, setShowLoginErrMessage] = useState(null);
  const [showHelp, setShowHelp] = useState(false);

  const handleShowHideHelp = () => {
    showHelp ? setShowHelp(false) : setShowHelp(true);
  };
  const memoHandleShowHideHelp = useCallback(handleShowHideHelp, [showHelp]);

  const handleLoginError = (loginError) => setShowLoginErrMessage(loginError);
  const memoHandleLoginError = useCallback(handleLoginError, [loginErrMessage]);

  return (
    <div className="container auth-container text-center ps-sm-7 pe-sm-7 ps-xl-15 pe-xl-15">
      <h1 className="fs-2 custom-font-family-teko custom-color-darkpurple">
        <BrandComponent customWidth={10} customHeight={10} />
      </h1>

      {loginErrMessage ? (
        <p className="custom-background-color-red custom-color-antiquewhite fs-7">
          {loginErrMessage}
        </p>
      ) : (
        <></>
      )}

      <div className="border border-secondary-subtle border-1 rounded-2 text-start pt-3 pb-3">
        <h2 className="ms-5 fw-bolder">Sign in</h2>

        <SignInFormComponent loginErrMessage={loginErrMessage} handleLoginError={memoHandleLoginError} />

        <NeedHelpComponent showHelp={showHelp} handleShowHideHelp={memoHandleShowHideHelp} />

        <div className="text-center mt-3">
          <p>---------- New to GymBro? ----------</p>
          <div className="mt-3">
            <Link to="/register">
              <button className="custom-background-color-antiquewhite fw-medium fs-7 p-1 ps-3 pe-3 border border-light rounded-1">
                Create your GymBro account
              </button>
            </Link>
          </div>
        </div>
      </div>
      <TrademarkComponent />
    </div>
  );
}
const SignInFormComponent = ({ handleLoginError }) => {
  const navigate = useNavigate();

  const emailRef = useRef(null);
  const passwordRef = useRef(null);

  const [emailInput, setEmailInput] = useState({
    style: validStyle,
    errMessage: null,
  });
  const [passwordInput, setPasswordInput] = useState({
    style: validStyle,
    errMessage: null,
  });

  const handleSubmit = async (e) => {
    e.preventDefault();

    if (emailRef.current.value && passwordRef.current.value) {
      setEmailInput({ style: validStyle, errMessage: null });
      setPasswordInput({ style: validStyle, errMessage: null });

      const signInServerResponse = await fetch("/users/sign-in", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          email: emailRef.current.value,
          password: passwordRef.current.value,
        }),
      });

      switch (signInServerResponse.status) {
        case 201:
          return navigate("/");
        case 400:
          handleLoginError(AppMessageConfig.auth["not-verified"]);
          break;
        case 401:
        case 404:
          handleLoginError(AppMessageConfig.auth["not-match"]);
          break;
        case 500:
          handleLoginError(AppMessageConfig.server.generic);
          break;
        default:
          return;
      }
    } else {
      handleLoginError(null); // remove login error message

      // manipulate styling of each input box for better user experience
      const inputRefs = [emailRef, passwordRef];

      for (const inputRef of inputRefs) {
        switch (inputRef.current.id) {
          case "email":
            if (inputRef.current.value === "") {
              setEmailInput({
                style: invalidStyle,
                errMessage: AppMessageConfig.auth.email,
              });
            } else {
              if (emailInput.style === invalidStyle) {
                setEmailInput({ style: validStyle, errMessage: null });
              }
            }

            break;
          case "password":
            if (inputRef.current.value === "") {
              setPasswordInput({
                style: invalidStyle,
                errMessage: AppMessageConfig.auth.password,
              });
            } else {
              if (passwordInput.style === invalidStyle) {
                setPasswordInput({ style: validStyle, errMessage: null });
              }
            }

            break;
          default:
            break;
        }
      }
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <div className="ps-5 pe-5">
        <label htmlFor="email" className="form-label fw-medium fs-13">
          Email
        </label>

        <input
          type="email"
          className={emailInput.style}
          id="email"
          name="email"
          ref={emailRef}
        />

        {emailInput.errMessage ? (
          <p className="fs-7 custom-color-red ms-1">{emailInput.errMessage}</p>
        ) : (
          <></>
        )}
      </div>

      <div className="ps-5 pe-5 mt-2">
        <label htmlFor="password" className="form-label fw-medium fs-13">
          Password
        </label>

        <input
          type="password"
          className={passwordInput.style}
          id="password"
          name="password"
          placeholder="At least any 6 characters"
          ref={passwordRef}
        />

        {passwordInput.errMessage ? (
          <p className="fs-7 custom-color-red ms-1">
            {passwordInput.errMessage}
          </p>
        ) : (
          <></>
        )}
      </div>

      <div className="ps-5 pe-5 mt-3 text-center">
        <button className="btn custom-background-color-darkpurple custom-color-antiquewhite custom-font-family-teko fw-bolder custom-width-92">
          Continue
        </button>
      </div>
    </form>
  );
};

export default React.memo(SignInFormComponent);
const NeedHelpComponent = ({ showHelp, handleShowHideHelp }) => {
    console.log("Need Help Component re-renders...");

    return (
        <div className="ps-5 mt-3">
        {showHelp ? (
          <CaretDownFill size={15} color="#3E0957" />
        ) : (
          <CaretRightFill size={15} color="#3E0957" />
        )}
        &nbsp;
        <Link className="fs-11" onClick={handleShowHideHelp}>
          Need help?
        </Link>
        {showHelp ? (
          <div>
            <Link className="fs-11 ps-4" to="/forgotpassword">
              Forgot your password
            </Link>
          </div>
        ) : (
          <></>
        )}
      </div>
    )
}

export default React.memo(NeedHelpComponent)

我尝试使用Callback()仅在loginErrMessage更改时调用handleLoginError(),但似乎不起作用。

javascript reactjs
1个回答
0
投票

首先要了解问题,SignIn 组件正在呈现,因为当您单击“需要帮助”按钮时,会在父级中调用 handleShowHideHelp 并更改其状态。状态更改会导致组件及其所有子组件(包括 SignInFormComponent)重新渲染,除非您正确记住 SignInFormComponent 的 props。

无论如何,要解决您的问题,您需要做的就是将 SignIn 组件中帮助条件的所有逻辑放入 NeedHelpComponent 中,因为它是唯一使用这些东西的组件。

因此,您应该从 SignIn 组件中删除 showHelp 状态、handleShowHideHelpmemoHandleShowHideHelp,现在 NeedHelpComponent 组件看起来像这样:

const NeedHelpComponent = (/* remove the props */) => {
    const [showHelp, setShowHelp] = useState(false);

    return (
        <div className="ps-5 mt-3">
        {showHelp ? (
          <CaretDownFill size={15} color="#3E0957" />
        ) : (
          <CaretRightFill size={15} color="#3E0957" />
        )}
        &nbsp;
        <Link className="fs-11" onClick={setShowHelp((prevState) => !prevState)}>
          Need help?
        </Link>
        {showHelp ? (
          <div>
            <Link className="fs-11 ps-4" to="/forgotpassword">
              Forgot your password
            </Link>
          </div>
        ) : (
          <></>
        )}
      </div>
    )
}

export default React.memo(NeedHelpComponent)

© www.soinside.com 2019 - 2024. All rights reserved.