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

使用createBrowserRouter时自定义Hook中useNavigate报错的解决咨询

解决方案

方案1:移除useUser中的useNavigate,改为外部传入跳转逻辑

把useUser里的登录/登出跳转逻辑抽离,让调用Hook的组件自行传入跳转函数,既解除对Router上下文的依赖,也让Hook更通用:

// 改造后的useUser
function useUser(onLoginSuccess, onLogoutSuccess) {
  const [user, setUser] = useState(null);
  const [permissions, setPermissions] = useState([]);

  const login = async (credentials) => {
    // 登录请求逻辑
    const res = await api.login(credentials);
    setUser(res.user);
    setPermissions(res.permissions);
    // 调用外部传入的跳转函数
    onLoginSuccess?.();
  };

  const logout = async () => {
    await api.logout();
    setUser(null);
    setPermissions([]);
    onLogoutSuccess?.();
  };

  return { user, permissions, login, logout };
}

在Router内部的组件中使用时,传入useNavigate的跳转方法:

// 登录组件示例
function LoginPage() {
  const navigate = useNavigate();
  const { login } = useUser(
    () => navigate('/dashboard'),
    () => navigate('/login')
  );

  // 登录表单逻辑...
}

如果需要在App组件中获取用户权限配置路由,可以单独写一个不依赖跳转的初始化函数:

// App.jsx
async function getInitialUser() {
  try {
    const res = await api.getCurrentUser();
    return { user: res.user, permissions: res.permissions };
  } catch {
    return { user: null, permissions: [] };
  }
}

function App() {
  const [userInfo, setUserInfo] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    getInitialUser().then(data => {
      setUserInfo(data);
      setLoading(false);
    });
  }, []);

  if (loading) return <div>Loading...</div>;

  const router = createBrowserRouter([
    // 根据userInfo.permissions配置路由
    {
      path: '/',
      element: <Layout />,
      children: [
        { path: 'dashboard', element: <Dashboard /> },
        // 其他路由...
      ]
    }
  ]);

  return <RouterProvider router={router} />;
}

方案2:将权限校验移到路由的loader/action或受保护路由组件中

不在App初始化路由时依赖用户权限,而是把校验逻辑放到路由的loader函数里,或者封装受保护路由组件:

方式A:用路由loader做权限校验

// 封装权限校验loader
async function requireAuth({ request }) {
  const user = await api.getCurrentUser();
  if (!user) {
    return redirect('/login?from=' + new URL(request.url).pathname);
  }
  // 校验权限
  if (!user.permissions.includes('dashboard')) {
    return redirect('/forbidden');
  }
  return user;
}

// 路由配置
const router = createBrowserRouter([
  {
    path: '/dashboard',
    element: <Dashboard />,
    loader: requireAuth
  },
  {
    path: '/login',
    element: <LoginPage />
  }
]);

// App.jsx直接渲染RouterProvider
function App() {
  return <RouterProvider router={router} />;
}

方式B:封装受保护路由组件

// ProtectedRoute.jsx
function ProtectedRoute({ children, requiredPermission }) {
  const { user, permissions } = useUser();
  const navigate = useNavigate();

  useEffect(() => {
    if (!user) {
      navigate('/login');
    } else if (requiredPermission && !permissions.includes(requiredPermission)) {
      navigate('/forbidden');
    }
  }, [user, permissions, requiredPermission, navigate]);

  if (!user || (requiredPermission && !permissions.includes(requiredPermission))) {
    return <div>Loading...</div>;
  }

  return children;
}

// 路由配置
const router = createBrowserRouter([
  {
    path: '/',
    element: <Layout />,
    children: [
      {
        path: 'dashboard',
        element: (
          <ProtectedRoute requiredPermission="dashboard">
            <Dashboard />
          </ProtectedRoute>
        )
      },
      // 其他路由...
    ]
  }
]);

// App.jsx
function App() {
  return <RouterProvider router={router} />;
}

方案3:将useUser中的useNavigate替换为Router的navigator实例

createBrowserRouter返回的router对象包含navigator属性,可通过useRouterContext获取,这样useUser仍能保留跳转逻辑,但必须在RouterProvider渲染的组件树中使用:

// useUser.js
import { useRouterContext } from 'react-router-dom';

function useUser() {
  const { navigator } = useRouterContext();
  const [user, setUser] = useState(null);
  const [permissions, setPermissions] = useState([]);

  const login = async (credentials) => {
    const res = await api.login(credentials);
    setUser(res.user);
    setPermissions(res.permissions);
    navigator('/dashboard');
  };

  const logout = async () => {
    await api.logout();
    setUser(null);
    setPermissions([]);
    navigator('/login');
  };

  return { user, permissions, login, logout };
}

注意:此方案下useUser不能在App组件初始化路由时调用,仅能在路由内部的组件中使用。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 00:32:51