React提交登录表单报Can't perform a React state update错误如何修复
问题根因
你的报错和useEffect的清理机制缺失、异步fetch请求没有中断逻辑直接相关,具体问题出在这几个地方:
- 调用
userContext.handleChange更新全局登录状态后,userAuth会变为true,此时Test组件渲染逻辑里的登录表单会被卸载,如果此时之前触发的fetch请求还没完成,后续请求完成后尝试调用setIsLoading、setError等状态更新方法,就会触发「已卸载组件不能更新状态」的报错,严重时直接导致页面白屏。 - 3处异步请求(组件挂载时的登录状态校验fetch、submitHandler里的校验注册状态fetch、getUserType里的查用户类型fetch)都没有配置中断逻辑,也没有判断组件是否还挂载就直接更新状态。
- 现有useEffect的清理函数是空的,没有释放异步任务资源。
修复方案
第一步:用AbortController中断fetch请求,新增挂载状态判断
修改后的完整代码如下:
const Test = (props) => { const [userLoginDetails, setUserLoginDetails] = useState({ userName: "", userPassword: "", userID: "", }); const [isLoading, setIsLoading] = useState(false); const [isLogIn, setIsLogin] = useState(false); const [isCompanyMember, setIsCompanyMember] = useState(false); const [error, setError] = useState(false); const [message, setMessage] = useState(""); const userContext = useContext(UserContext); const userAuth = userContext.authed; // 存储所有fetch请求的AbortController实例 const abortsRef = useRef([]); const onChangeHandler = (e) => { let { name, value } = e.target; let updatedState = { ...userLoginDetails }; updatedState[name] = value; setUserLoginDetails(updatedState); }; const getUserType = async () => { // 新建中断控制器 const controller = new AbortController(); abortsRef.current.push(controller); try { const bodyContent = { userName: userLoginDetails.userName, }; const response = await fetch(`${URL}/members/${userLoginDetails.userID}`, { method: "post", headers: { "Content-Type": "application/json", }, body: JSON.stringify(bodyContent), credentials: "include", // 绑定中断信号 signal: controller.signal }); if (!response.ok) { setError(true); setIsLoading(false); setMessage("something went wrong! try again!"); return; } const body = await response.json(); userContext.handleChange(body.data.result, true, userLoginDetails.userName); setIsLogin(true); setError(false); setIsLoading(false); setMessage(""); } catch (err) { // 如果是主动中断的请求,不处理报错 if (err.name !== 'AbortError') { setError(true); setIsLoading(false); setMessage("connection problem"); } } }; const submitHandler = async (e) => { e.preventDefault(); // 先中断之前所有未完成的请求,避免重复提交触发多次状态更新 abortsRef.current.forEach(ctrl => ctrl.abort()); abortsRef.current = []; setIsLoading(true); setError(false); const controller = new AbortController(); abortsRef.current.push(controller); fetch(`someURL`, { method: "post", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ UserName: userLoginDetails.userName, PassWord: userLoginDetails.userPassword, Id: userLoginDetails.userID, }), signal: controller.signal }) .then((response) => { return response.json(); }) .then((response) => { if (response) { setIsCompanyMember(true); } else { setError(true); setIsLoading(false); setMessage("combination of user,id an password didnt work!"); } }) .catch((err) => { if (err.name !== 'AbortError') { setError(true); setIsLoading(false); setMessage("connection problem"); } }); }; useEffect(() => { const controller = new AbortController(); abortsRef.current.push(controller); fetch(`${URL}/login/`, { credentials: "include", signal: controller.signal }) .then((response) => { return response.json(); }) .then((response) => { console.log(response); if (response.data.loggedIn === true) { setIsLogin(true); setError(false); setMessage(""); userContext.handleChange( response.data.userType, true, response.data.userName ); } }) .catch(err => { if (err.name === 'AbortError') return; console.error(err); }); return () => { // 组件卸载时中断所有未完成的请求 abortsRef.current.forEach(ctrl => ctrl.abort()); }; }, []); useEffect(() => { if (isCompanyMember) { getUserType(); } }, [isCompanyMember]); return ( <nav> {!isLogIn && error && ( <div role="alert" style={{ margin: "auto" }}> {message} <button type="button" className="close" data-dismiss="alert" aria-label="Close" > <span aria-hidden="true">×</span> </button> </div> )} <ul> <li> {isLoading && <TinySpinner />} {!userAuth && ( <form id={classes.login} onSubmit={submitHandler}> <div> <input type="text" name="userName" id={classes.login_username} placeholder="Username" autoComplete="username" onChange={onChangeHandler} value={userLoginDetails.userName} ></input> </div> <div> <input type="password" name="userPassword" id={classes.login_password} placeholder="Password" autoComplete="current-password" onChange={onChangeHandler} value={userLoginDetails.userPassword} ></input> </div> <div> <input type="text" name="userID" id={classes.login_userid} placeholder="ID" autoComplete="id" onChange={onChangeHandler} value={userLoginDetails.userID} ></input> </div> <div> <input type="submit" value="Log in"></input> </div> </form> )} </li> </ul> </nav> ); }; export default Test;
第二步:额外优化
可以在提交按钮点击后暂时禁用提交按钮,避免用户重复点击触发多次请求:
<input type="submit" value="Log in" disabled={isLoading}></input>
内容的提问来源于stack exchange,提问作者md86
相关产品推荐
相关产品推荐

