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

useReducer初始状态异常:依赖localStorage时路由跳转错误

问题原因分析

你遇到的核心问题是组件渲染时序导致的路由误跳转:

  • useReducer 初始状态设置为 { user: null },而从 localStorage 读取并更新用户状态的逻辑放在了 useEffect 中。
  • useEffect 是组件首次渲染完成后才执行的,因此 App 组件在首次渲染时拿到的 user 始终是 null,触发路由跳转到 /login;即使后续 useEffect 成功更新了用户状态,也无法挽回已经完成的跳转。

解决方案

方案一:同步初始化状态(适合token解码为同步操作的场景)

直接在 useReducer 初始化时读取 localStorage 并解析token,让组件一开始就拥有正确的用户状态,避免首次渲染时的 null 值。

修改 AuthContext.jsx:

import { createContext, useReducer } from "react"
import jwt from "jwt-decode"

export const AuthContext = createContext();

export const authReducer = (state, action) => {    
  switch(action.type) {
    case "LOGIN":
      return {
        user: action.payload
      }
    case "LOGOUT":
      return {
        user: null
      }
    default:
      return state
  }
}

// 同步生成初始状态
const getInitialState = () => {
  const refreshToken = localStorage.getItem('refresh_token');
  if (refreshToken) {
    try {
      const decodedJwt = jwt(refreshToken);
      // 可选:添加token过期判断
      // if (decodedJwt.exp * 1000 > Date.now()) {
      //   return { user: { nama: decodedJwt.nama, role: decodedJwt.role } }
      // } else {
      //   localStorage.removeItem('refresh_token');
      //   return { user: null }
      // }
      return { user: { nama: decodedJwt.nama, role: decodedJwt.role } }
    } catch (err) {
      // 解码失败,清除无效token
      localStorage.removeItem('refresh_token');
      return { user: null }
    }
  }
  return { user: null }
}

export const AuthContextProvider = ({ children }) => {    
  const [state, dispatch] = useReducer(authReducer, getInitialState());

  console.log('AuthContext state: ', state);

  return (
    <AuthContext.Provider value={{...state, dispatch}}>
      {children}
    </AuthContext.Provider>
  )
}

方案二:添加加载状态(适合需要异步验证token的场景)

如果你的 refresh_token 需要调用后端接口验证有效性(而非单纯解码),同步初始化无法满足需求,此时需要添加加载状态,在验证完成前不进行路由跳转。

步骤1:修改 AuthContext.jsx

import { createContext, useReducer, useEffect } from "react"
import jwt from "jwt-decode"

export const AuthContext = createContext();

export const authReducer = (state, action) => {    
  switch(action.type) {
    case "LOGIN":
      return {
        ...state,
        user: action.payload
      }
    case "LOGOUT":
      return {
        ...state,
        user: null
      }
    case "SET_LOADING":
      return {
        ...state,
        loading: action.payload
      }
    default:
      return state
  }
}

export const AuthContextProvider = ({ children }) => {    
  const [state, dispatch] = useReducer(authReducer, { 
    user: null,
    loading: true // 初始加载状态设为true
  });

  useEffect(() => {
    const initAuth = async () => {
      dispatch({ type: "SET_LOADING", payload: true });
      const refreshToken = localStorage.getItem('refresh_token');

      if (refreshToken) {
        try {
          const decodedJwt = jwt(refreshToken);
          // 若需异步验证,替换为接口调用逻辑
          // const res = await fetch('/api/validate-token', {
          //   method: 'POST',
          //   body: JSON.stringify({ refreshToken })
          // });
          // const data = await res.json();
          // if (data.valid) {
          //   dispatch({ type: "LOGIN", payload: { nama: data.nama, role: data.role } });
          // } else {
          //   localStorage.removeItem('refresh_token');
          // }
          dispatch({
            type: "LOGIN",
            payload: { nama: decodedJwt.nama, role: decodedJwt.role }
          });
        } catch (err) {
          localStorage.removeItem('refresh_token');
        }
      }
      dispatch({ type: "SET_LOADING", payload: false });
    };

    initAuth();
  }, []);

  console.log('AuthContext state: ', state);

  return (
    <AuthContext.Provider value={{...state, dispatch}}>
      {children}
    </AuthContext.Provider>
  )
}

步骤2:修改 App.jsx

import React from 'react'
import { Navigate, BrowserRouter, Routes, Route } from 'react-router-dom'
import Index from './pages/Index.jsx'
import Login from "./pages/auth/Login.jsx"
import { useAuthContext } from './hooks/useAuthContext.js'

const App = () => {
  const { user, loading } = useAuthContext();

  // 加载中时显示占位内容,不执行路由判断
  if (loading) {
    return <div>加载中...</div>; // 可替换为自定义加载组件
  }

  return (
    <BrowserRouter>
      <Routes>
        <Route
          path='/'
          element={user ? <Index /> : <Navigate to='/login' />}
        />
        <Route
          path='/login'
          element={<Login/>}
        />
      </Routes>
    </BrowserRouter>
  )
}

export default App

内容的提问来源于stack exchange,提问作者Tko Code

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 16:25:06