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

从useAuth解构onLogout遇Null错误,ProtectedRoute使用正常求助

问题原因分析

这个错误的核心是Context的作用域问题:你在App组件的根级别直接调用了useAuth(),但此时AuthProvider还没有完成渲染,所以useContext(AuthContext)拿到的是你创建Context时传入的默认值null,自然无法解构onLogout属性。

而ProtectedRoute之所以能正常工作,是因为它是作为AuthProvider的children被渲染的——也就是说,当ProtectedRoute执行useAuth()时,它已经处于AuthContext.Provider的作用域范围内,能正确获取到Provider提供的value对象。

解决方案

我们需要把所有依赖AuthContext的代码,放到AuthProvider的作用域内部。这里有两种清晰的修改方式:

方式一:拆分组件,将依赖Context的逻辑移到Provider内部

把App中用到onLogout的部分拆成一个独立子组件,比如AppContent,然后让这个子组件在AuthProvider内部渲染:

import React, { createContext, useContext } from "react";
import { Navigate, Route, Routes, useNavigate } from "react-router-dom";
import { useDispatch } from "react-redux";
import { logout, userLogin, userRegister } from "./features/cv";
import { cookieCutter } from "./utils/cookie";

const AuthContext = createContext(null);

const AuthProvider = ({ children }) => {
  const token = cookieCutter("token");
  const dispatch = useDispatch();
  const navigate = useNavigate();

  const handleLogin = (data) => {
    dispatch(userLogin(data));
    navigate("/table");
  };

  const handleRegister = (data) => {
    dispatch(userRegister(data));
    navigate("/table");
  };

  const handleLogout = () => {
    dispatch(logout());
    navigate("/login");
  };

  const value = {
    token,
    onLogin: handleLogin,
    onRegister: handleRegister,
    onLogout: handleLogout,
  };

  return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
};

const useAuth = () => {
  return useContext(AuthContext);
};

// 单独定义ProtectedRoute,避免嵌套在App内部
const ProtectedRoute = ({ children }) => {
  const { token } = useAuth();

  if (!token) {
    return <Navigate to="/login" replace />;
  }
  return children;
};

// 拆分出AppContent,放在AuthProvider内部
const AppContent = () => {
  const userString = JSON.parse(cookieCutter("user") ?? "{}");
  const { onLogout } = useAuth();

  // 这里可以渲染你的导航栏、调用onLogout的按钮等
  return (
    <div>
      {/* 示例:添加退出登录按钮 */}
      <button onClick={onLogout}>退出登录</button>
      <Routes>
        <Route
          path="/"
          element={
            <ProtectedRoute>
              <Create />
            </ProtectedRoute>
          }
        />
        <Route
          path="/table"
          element={
            <ProtectedRoute>
              <Table />
            </ProtectedRoute>
          }
        />
        <Route
          path="/preview"
          element={
            <ProtectedRoute>
              <Preview />
            </ProtectedRoute>
          }
        />
        <Route path="/login" element={<Login />} />
        <Route path="/register" element={<Register />} />
      </Routes>
    </div>
  );
};

function App() {
  return ( 
    <AuthProvider>
      <AppContent />
    </AuthProvider>
 )
}

方式二:将useAuth的调用移到Provider包裹的JSX内部

如果你不想拆分组件,也可以把依赖onLogout的逻辑直接写到AuthProvider的children里:

function App() {
  // 不要在这里调用useAuth()!

  return ( 
    <AuthProvider>
      {/* 把需要useAuth的逻辑移到这里 */}
      {() => {
        const userString = JSON.parse(cookieCutter("user") ?? "{}");
        const { onLogout } = useAuth();

        return (
          <div>
            <button onClick={onLogout}>退出登录</button>
            <Routes>
              {/* 路由定义和之前一致 */}
            </Routes>
          </div>
        )
      }}
    </AuthProvider>
 )
}

不过这种方式不如拆分组件清晰,更推荐第一种方案。

额外建议

另外,建议把ProtectedRoute从App组件内部移出来单独定义——嵌套定义组件会导致每次App渲染时都创建一个新的ProtectedRoute组件,可能引发不必要的重渲染,也不利于代码复用。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 17:36:09