You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 02:20:58