我正在为我的应用程序构建一个简单的登录页面,其结构如下所示:
<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" />
)}
<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(),但似乎不起作用。
首先要了解问题,SignIn 组件正在呈现,因为当您单击“需要帮助”按钮时,会在父级中调用 handleShowHideHelp 并更改其状态。状态更改会导致组件及其所有子组件(包括 SignInFormComponent)重新渲染,除非您正确记住 SignInFormComponent 的 props。
无论如何,要解决您的问题,您需要做的就是将 SignIn 组件中帮助条件的所有逻辑放入 NeedHelpComponent 中,因为它是唯一使用这些东西的组件。
因此,您应该从 SignIn 组件中删除 showHelp 状态、handleShowHideHelp 和 memoHandleShowHideHelp,现在 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" />
)}
<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)