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

React Router调用navigate('/')后组件未渲染,URL更新但页面未切换

登录后navigate('/')未完全生效的问题

登录成功后调用navigate('/'),URL从/get-started/signup变为/,导航栏登录按钮替换为用户头像,但<Home/>组件未渲染,<SignUp/>组件也未卸载。

相关代码片段

组件内登录处理函数

const { isAuthenticating, error, handleAuth } = useAuth()

const handleSignIn = async (e) => {
  e.preventDefault()
  const success = await handleAuth(signin, emailRef.current.value, passwordRef.current.value)
  if (success) {
    navigate('/')
  }
}

useAuth.js中的handleAuth实现

const { isAuthenticating, error } = useSelector(state => state.user)

const handleAuth = async (authType, email, password) => {
  try {
    const action = await dispatch(authType({ email, password }))
    if (authType.fulfilled.match(action)) {
      console.log('worked')
      return true;
    }
  } catch (err) {
    console.log(err)
    return false;
  }
}

signin异步Thunk

export const signin = createAsyncThunk(
  'user/signin',
  async({ email, password }, { rejectWithValue }) => {
    try {
      const response = await signInWithEmailAndPassword(auth, email, password)
      const accessToken = await response.user.getIdToken()

      return {
        uid: response.user.uid,
        email: response.user.email,
        accessToken
      }
    } catch (error) {
      return rejectWithValue(error.message)
    }
  }
)

Redux User Slice配置

初始状态

const initialState = {
  user: null,
  error: {
    type: null,
    message: null
  },
  isAuthenticating: false,
}

Extra Reducers处理

extraReducers: (builder) => {
  builder
    .addCase(signin.pending, (state) => {
      state.isAuthenticating = true;
      state.error = initialState.error;
    })
    .addCase(signin.fulfilled, (state, action) => {
      state.user = action.payload;
      state.isAuthenticating = false;
      localStorage.setItem('accessToken', action.payload.accessToken);
      localStorage.setItem('email', action.payload.email);
      localStorage.setItem('uid', action.payload.uid);
    })
    .addCase(signin.rejected, (state, action) => {
      state.isAuthenticating = false;
      state.error = handleError(action.payload);
    });
},

路由与私有路由配置

路由定义

const router = createBrowserRouter([
  {
    path: '/',
    element: <RootLayout />,
    errorElement: <Error />,
    children: [
      {
        index: true,
        element: <PrivateRoute element={<Home />} />,
      },
      {
        path: 'get-started',
        element: <LandingLayout />,
        children: [
          {
            index: true,
            element: <GetStarted />,
          },
          {
            path: 'signup',
            element: <SignUp />
          },
        ]
      }
    ]
  }
])

PrivateRoute组件(原错误版本)

const PrivateRoute = ({ element }) => {
  const user = useSelector(state => state.user)

  return user ? element : <Navigate to="/get-started" />
}

RootLayout组件

const RootLayout = () => {
  return (
    <>
      <Nav />
      <Outlet />
    </>
  )
}

问题原因分析

核心问题出在**PrivateRoute的状态判断逻辑错误**:

  • useSelector(state => state.user)获取的是整个user slice对象(包含user、error、isAuthenticating属性),而非实际的用户信息字段state.user.user。
  • 由于slice对象始终是一个非空对象,return user ? element : ...的判断永远为真,导致PrivateRoute无论用户是否登录都会返回<Home/>组件。但登录后路由切换时,Redux状态更新的时机与路由渲染的时机不匹配,加上路由层级的嵌套关系,最终出现URL更新但组件未正确切换的异常。

解决方案

1. 修正PrivateRoute的状态判断逻辑

将PrivateRoute中获取用户状态的代码改为实际用户信息字段,并添加replace属性优化重定向行为:

const PrivateRoute = ({ element }) => {
  // 正确获取store中的用户信息
  const user = useSelector(state => state.user.user)

  // 用户存在则渲染目标组件,否则重定向到登录页面
  return user ? element : <Navigate to="/get-started" replace />
}

2. 补充初始化时的用户状态恢复逻辑

为避免刷新页面后用户状态丢失,在user-slice的初始状态中从localStorage读取已保存的用户数据:

const initialState = {
  // 从localStorage恢复用户状态
  user: localStorage.getItem('uid') ? {
    uid: localStorage.getItem('uid'),
    email: localStorage.getItem('email'),
    accessToken: localStorage.getItem('accessToken')
  } : null,
  error: {
    type: null,
    message: null
  },
  isAuthenticating: false,
}

3. 验证路由切换逻辑

确保handleSignIn中navigate('/')的调用是在Redux状态更新完成后执行的(当前代码通过await dispatch(authType(...))已实现这一点,无需修改)。

完成以上修改后,登录成功调用navigate('/')时,PrivateRoute会正确识别用户已登录,渲染<Home/>组件,同时卸载<SignUp/>组件,路由切换功能完全生效。

内容的提问来源于stack exchange,提问作者Zainab Hussain

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 03:25:56