React页面刷新后保持当前页面状态的技术实现问题
React页面刷新后跳回首页的修复方案
问题描述
我有一个包含首页(着陆页)和仪表盘的React应用,希望刷新页面后仪表盘组件能保持当前状态,但每次刷新都会跳转回首页。
相关代码
AuthSlice
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'; import authService from './authService'; import { jwtDecode } from 'jwt-decode'; const initialState = { isAuthenticated: false, data:{ user: {} } , loading: false, isError: false, isSuccess: false, isLoading: false, message: '', getcpd: null, employeeCode: { portalCode: '' }, errors: null, }; console.log('initial-redux state : ',initialState.user) // Async thunk to initialize authentication state during application startup export const initializeAuthState = createAsyncThunk( 'auth/initializeAuthState', async () => { console.log('checking local storage...'); const storedToken = localStorage.getItem('jwtToken'); if (storedToken) { console.log('token present in local storage!'); const decoded = jwtDecode(storedToken); try { const response = await authService.retrieveUserData(decoded.id); console.log('userData retrieved during initialization', response); // Return the payload to be used in extraReducers return { decoded, userData: response.data, }; } catch (error) { console.error('Error during authentication state initialization:', error); throw error; // Propagate the error } } else { console.log('No token in local storage!'); return null; // Return null if no token is present } } ); export const register = createAsyncThunk( 'auth/register', async (userData, { rejectWithValue }) => { try { await authService.register(userData); return { success: true }; } catch (error) { return rejectWithValue(error.response.data); } } ); // Login user export const login = createAsyncThunk( 'auth/login', async (user, thunkAPI) => { try { const response = await authService.login(user); console.log('login response from service', response); if (response.token) { // Use jwtDecode directly const decoded = jwtDecode(response.token); console.log('decoded:', decoded); localStorage.setItem('jwtToken', response.token); authService.setAuthToken(response.token); // Dispatch the setCurrentUser action using thunkAPI thunkAPI.dispatch(setCurrentUser({ ...decoded, // Include the decoded token properties ...response.data, // Include additional user information from response.data })); // Return the response data return response; } else { console.log('Token is not available in the response data.'); // Handle the absence of token, you may want to throw an error or handle it as needed throw new Error('Token is not available in the response data.'); } } catch (error) { // Handle the error and return a rejected value using rejectWithValue const message = (error.response && error.response.data && error.response.data.message) || error.message || error.toString(); return thunkAPI.rejectWithValue(message); } } ); export const authSlice = createSlice({ name: 'auth', initialState, reducers: { setCurrentUser: (state, action) => { console.log('setcurrentUser payload:', action.payload) state.isAuthenticated = !!Object.keys(action.payload).length; state.user = action.payload; }, setUserLoading: (state) => { state.loading = true; }, resetErrors: (state) => { state.errors = null; }, }, extraReducers: (builder) => { builder .addCase(initializeAuthState.fulfilled, (state, action) => { state.isAuthenticated = true; state.data.user = { ...action.payload.decoded, ...action.payload.userData, }; }) .addCase(initializeAuthState.rejected, (state, action) => { console.error('Error during authentication state initialization:', action.error); }) .addCase(login.fulfilled, (state) => { state.loading = false; }) .addCase(login.rejected, (state, action) => { state.loading = false; state.errors = action.payload; }) }, }); export const { setCurrentUser, setUserLoading, resetErrors } = authSlice.actions; export default authSlice.reducer;
App.jsx
export default function App() { const dispatch = useDispatch(); //Call initializeAuthState when the app starts useEffect(() => { dispatch(initializeAuthState()); }); useScrollToTop(); return ( <ThemeProvider> <ErrorBoundary> <Router /> </ErrorBoundary> </ThemeProvider> ); }
登录handleSubmit函数
const handleSubmitLogin = async () => { // Dispatch the login action const actionResult = await dispatch(login(userLogin)); // Check if the login action was successful if (login.fulfilled.match(actionResult)) { console.log('isAuthenticated:', isAuthenticated); // Add this line console.log('user:', user); // Add this line // If already authenticated, stay on the current page if (isAuthenticated) { console.log('Already authenticated, not navigating.'); // Add this line return; } // If not authenticated, navigate to the home page router.push('/home'); } };
路由文件
export default function Router() { // const { isSuccess } = useSelector((state) => state.auth); const storedIsSuccess = localStorage.getItem('isSuccess') === 'true'; const { isAuthenticated } = useSelector((state) => state.auth); const { user } = useSelector((state) => state.auth ); const location = useLocation(); const isReload = location.state && location.state.isReload; useEffect(() => { if (isAuthenticated && isReload) { // Prevent navigation away from the current page during refresh return; } }, [isAuthenticated, isReload]); console.log(isAuthenticated && 'user authenticated'); console.log('user before login starts',user); return ( <Routes> <Route path="/" element={ (user && isAuthenticated) ? ( <DashboardLayout> <DashboardLayout> <Suspense> <Outlet /> </Suspense> </DashboardLayout> ) : ( <HomePage /> ) } >
修复步骤
1. 统一Auth状态中的user存储位置
登录时setCurrentUser将用户信息存在state.user,但初始化认证状态时却存在state.data.user,导致组件获取的user始终是空对象,路由判断失败。修改AuthSlice中initializeAuthState.fulfilled的逻辑:
.addCase(initializeAuthState.fulfilled, (state, action) => { state.isAuthenticated = true; // 将用户信息放到state根层级,和登录逻辑保持一致 state.user = { ...action.payload.decoded, ...action.payload.userData, }; })
2. 添加初始化加载状态,避免页面闪烁
初始化认证是异步操作,页面加载时初始状态为未认证,会先渲染首页,等初始化完成后才更新状态。需要添加加载状态:
- 在
AuthSlice的initialState中添加initializing: true - 更新
extraReducers处理初始化的不同状态:
// initialState新增 initializing: true, // extraReducers中补充 .addCase(initializeAuthState.pending, (state) => { state.initializing = true; }) .addCase(initializeAuthState.fulfilled, (state, action) => { state.isAuthenticated = true; state.user = { ...action.payload.decoded, ...action.payload.userData, }; state.initializing = false; }) .addCase(initializeAuthState.rejected, (state, action) => { console.error('Error during authentication state initialization:', action.error); state.initializing = false; })
3. 路由中处理加载状态并修正结构
在路由组件中获取initializing状态,先显示加载组件,再根据认证状态渲染对应内容,同时修复重复的DashboardLayout:
export default function Router() { const { isAuthenticated, user, initializing } = useSelector((state) => state.auth ); return ( <Routes> <Route path="/" element={ initializing ? ( <div>加载中...</div> {/* 替换为你的加载组件 */} ) : ( isAuthenticated ? ( <DashboardLayout> <Suspense> <Outlet /> </Suspense> </DashboardLayout> ) : ( <HomePage /> ) ) } > {/* 仪表盘子路由示例 */} <Route index element={<DashboardHome />} /> <Route path="stats" element={<DashboardStats />} /> </Route> {/* 登录页路由(如果需要) */} <Route path="/login" element={<LoginPage />} /> </Routes> ); }
4. 修正App.jsx中的useEffect依赖
给useEffect添加空依赖数组,避免初始化逻辑重复触发:
useEffect(() => { dispatch(initializeAuthState()); }, [dispatch]);
5. 调整登录后的导航逻辑
登录成功后应该跳转到仪表盘首页,而不是着陆页,修改handleSubmitLogin中的导航代码:
// 替换原来的router.push('/home') router.push('/');
内容的提问来源于stack exchange,提问作者8SINS
相关产品推荐
相关产品推荐

