React应用useState更新前组件渲染致登录跳转异常问题
问题解决:React登录API状态更新不及时与跳转过早问题
核心问题分析
- useApi Hook 缺陷:依赖项为空数组导致参数变化时不重新请求;
loading状态逻辑颠倒,无法标识加载过程;响应数据类型存储错误。 - 登录流程逻辑错误:登录表单未验证就跳转路由,PrivateRoute因初始
isAuth=false直接触发回跳。 - PrivateRoute 未处理加载状态:未等待API请求完成就执行跳转,忽略请求中的过渡状态。
修复后的代码实现
1. 修正 useApi Hook
import axios from "axios"; import { useEffect, useState } from "react"; // T: 请求参数类型,R: 响应数据类型 const useApi = <T, R = unknown>(url: string, method: string, payload?: T) => { const [apiData, setApiData] = useState<R>(); const [loading, setLoading] = useState(false); const [error, setError] = useState<string>(); useEffect(() => { const fetchData = async () => { setLoading(true); try { const config = { method, url, // 根据请求方法区分参数传递方式 ...(method.toUpperCase() === "GET" ? { params: payload } : { data: payload }) }; const response = await axios(config); setApiData(response.data); } catch (e: any) { setError(e?.response?.data?.message || e.message || "请求失败"); } finally { setLoading(false); } }; fetchData(); }, [url, method, payload]); // 添加依赖项,参数变化时重新请求 return { apiData, loading, error }; }; export default useApi;
2. 重构登录表单组件(SignIn)
import { useState, useEffect } from "react"; import { useNavigate } from "react-router-dom"; import useApi from "../../api/useApi"; export const SignIn = () => { const [userInfo, setUserInfo] = useState({ user: "", password: "", }); const navigate = useNavigate(); // 提交时调用登录API const { apiData, loading, error } = useApi< typeof userInfo, { status: number; token?: string } >(import.meta.env.VITE_SIGN_IN, "POST", userInfo); // 监听API响应,验证成功后跳转 useEffect(() => { if (apiData?.status === 200) { // 持久化存储token localStorage.setItem("authToken", apiData.token); // 跳转到受保护的仪表盘页面 navigate("/user/dashboard", { replace: true }); } }, [apiData, navigate]); const submitForm = (e: React.FormEvent) => { e.preventDefault(); }; const updateUser = (newVal: string) => { setUserInfo((prev) => ({ ...prev, user: newVal })); }; const updatePassword = (newVal: string) => { setUserInfo((prev) => ({ ...prev, password: newVal })); }; return ( <form onSubmit={submitForm}> <label>username</label> <input type="text" value={userInfo.user} onChange={(e) => updateUser(e.target.value)} disabled={loading} /> <label>password</label> <input type="password" value={userInfo.password} onChange={(e) => updatePassword(e.target.value)} disabled={loading} /> {error && <p style={{ color: "red" }}>{error}</p>} <button type="submit" disabled={loading}> {loading ? "登录中..." : "提交"} </button> </form> ); };
3. 修正 PrivateRoute 组件
import { useState, useEffect } from "react"; import { Navigate, useLocation } from "react-router-dom"; import UserDashboard from "./UserDashboard"; import useApi from "../../api/useApi"; export const PrivateRoute = () => { const location = useLocation(); const [isAuth, setIsAuth] = useState<boolean | null>(null); // 初始为null表示未完成验证 const token = localStorage.getItem("authToken"); // 可选:验证token有效性 const { apiData, loading } = useApi< { token: string }, { valid: boolean } >(import.meta.env.VITE_VALIDATE_TOKEN, "POST", { token }); useEffect(() => { if (!loading) { // 结合本地存储和API验证结果判断权限 if (token && (apiData?.valid ?? true)) { setIsAuth(true); } else { setIsAuth(false); } } }, [token, apiData, loading]); // 加载中显示过渡状态 if (isAuth === null || loading) { return <div>加载中...</div>; } return isAuth ? ( <UserDashboard /> ) : ( <Navigate to="/sign-in" state={{ from: location.pathname }} replace /> ); };
关键修复点说明
- useApi 优化:修正
loading状态逻辑,添加正确依赖项,适配不同请求方法的参数传递。 - 登录流程调整:在表单内完成API验证,成功后再跳转,禁用加载中元素防止重复提交。
- PrivateRoute 改进:初始状态设为
null避免误跳转,处理加载状态,结合本地存储实现持久化认证。
内容的提问来源于stack exchange,提问作者Ali Shahen
相关产品推荐
相关产品推荐

