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

如何解决useNavigate()仅能在<Router>组件上下文使用的错误?

解决useNavigate()只能在<Router>组件上下文使用的错误

我使用React TypeScript开发,通过UserContext实现用户注册与登录功能,在_app.tsx文件中尝试包裹UserProvider时出现如下错误:

error - Error: useNavigate() may be used only in the context of a component.

UserProvider相关代码

/* eslint-disable @typescript-eslint/no-non-null-asserted-optional-chain */
import { createContext, useEffect, useState } from "react";
import { loginAPI, registerAPI } from "../services/AuthService";

import React from "react";
import { UserProfile } from "../models/User";
import axios from "axios";
import { toast } from "react-toastify";
import { useNavigate } from "react-router-dom";

type UserContextType = {
  user: UserProfile | null;
  token: string | null;  
  loginUser: (username: string, password: string) => void;  
  isLoggedIn: () => boolean;
};

type Props = { children: React.ReactNode };

const UserContext = createContext<UserContextType>({} as UserContextType);

export const UserProvider = ({ children }: Props) => {
  const navigate = useNavigate();
  const [token, setToken] = useState<string | null>(null);
  const [user, setUser] = useState<UserProfile | null>(null);
  const [isReady, setIsReady] = useState(false);

  useEffect(() => {
    const user = localStorage.getItem("user");
    const token = localStorage.getItem("token");
    if (user && token) {
      setUser(JSON.parse(user));
      setToken(token);
      axios.defaults.headers.common["Authorization"] = "Bearer " + token;
    }
    setIsReady(true);
  }, []);

  const loginUser = async (username: string, password: string) => {
    await loginAPI(username, password)
      .then((res) => {
        if (res) {
          localStorage.setItem("token", res?.data.token);
          const userObj = {
            username: res?.data.username,
            email: res?.data.email,
          };
          localStorage.setItem("user", JSON.stringify(userObj));
          setToken(res?.data.token!);
          setUser(userObj!);
          toast.success("Login Success!");
          navigate("/");
        }
      })
      .catch((e) => toast.warning("Server error occured"));
  };

  const isLoggedIn = () => {
    return !!user;
  };

  return (
    <UserContext.Provider
      value={{ loginUser, user, token, isLoggedIn  }}
    >
      {isReady ? children : null}
    </UserContext.Provider>
  );
};

export const useAuth = () => React.useContext(UserContext);

_app.tsx中包裹UserProvider的代码

/* eslint-disable lines-around-comment */
// ** React Perfect Scrollbar Style
import 'react-perfect-scrollbar/dist/css/styles.css'
// ** Global css styles
import '../../styles/globals.css'
import "react-toastify/dist/ReactToastify.css"

// ** Contexts
import { SettingsConsumer, SettingsProvider } from 'src/context/settingsContext'

import type { AppProps } from 'next/app'
// ** Emotion Imports
import { CacheProvider } from '@emotion/react'
import type { EmotionCache } from '@emotion/cache'
// ** Next Imports
import Head from 'next/head'
// ** Loader Import
import NProgress from 'nprogress'
import type { NextPage } from 'next'
import { Router } from 'next/router'
import ThemeComponent from 'src/@core/theme/ThemeComponent'
// ** Component Imports
import UserLayout from 'src/layouts/UserLayout'
import { UserProvider } from 'src/context/useAuth'
// ** Utils Imports
import { createEmotionCache } from 'src/@core/utils/create-emotion-cache'
// ** Config Imports
import themeConfig from 'src/configs/themeConfig'

// ** Extend App Props with Emotion
type ExtendedAppProps = AppProps & {
  Component: NextPage
  emotionCache: EmotionCache
}

const clientSideEmotionCache = createEmotionCache()

// ** Pace Loader
if (themeConfig.routingLoader) {
  Router.events.on('routeChangeStart', () => {
    NProgress.start()
  })
  Router.events.on('routeChangeError', () => {
    NProgress.done()
  })
  Router.events.on('routeChangeComplete', () => {
    NProgress.done()
  })
}

// ** Configure JSS & ClassName
const App = (props: ExtendedAppProps) => {
  const { Component, emotionCache = clientSideEmotionCache, pageProps } = props

  // Variables
  const getLayout = Component.getLayout ?? (page => <UserLayout>{page}</UserLayout>)

  return (
    <>
  <UserProvider>
    <CacheProvider value={emotionCache}>
      <Head>
        <title>{`${themeConfig.templateName} - Material Design React Admin Template`}</title>
        <meta
          name='description'
          content={`${themeConfig.templateName} – Material Design React Admin Dashboard Template – is the most developer friendly & highly customizable Admin Dashboard Template based on MUI v5.`}
        />
        <meta name='keywords' content='Material Design, MUI, Admin Template, React Admin Template' />
        <meta name='viewport' content='initial-scale=1, width=device-width' />
      </Head>

      <SettingsProvider>
        <SettingsConsumer>
          {({ settings }) => {
              return <>
<ThemeComponent settings={settings}>{getLayout(<Component {...pageProps} />)}</ThemeComponent>
</>
          }}
          </SettingsConsumer>

      </SettingsProvider>
        </CacheProvider>
  </UserProvider>

      </>

  )
}
export default App

问题原因

UserProvider中直接调用了useNavigate(),但当前UserProvider的渲染层级在Router组件之外,无法获取路由上下文。在你的项目中,Router大概率是在UserLayout或ThemeComponent内部提供的,导致UserProvider处于路由上下文的外层。

解决方法

方法一:调整UserProvider的包裹层级

将UserProvider移到Router上下文内部,确保它能获取到路由实例。修改_app.tsx的return结构:

return (
  <CacheProvider value={emotionCache}>
    <Head>
      {/* 头部内容保持不变 */}
    </Head>
    <SettingsProvider>
      <SettingsConsumer>
        {({ settings }) => {
          return (
            <ThemeComponent settings={settings}>
              {/* 将UserProvider放到UserLayout内部,确保处于Router上下文 */}
              <UserProvider>
                {getLayout(<Component {...pageProps} />)}
              </UserProvider>
            </ThemeComponent>
          )
        }}
      </SettingsConsumer>
    </SettingsProvider>
  </CacheProvider>
)

方法二:分离导航逻辑,不在UserProvider中使用useNavigate

如果不想调整层级,可以把导航逻辑从UserProvider中抽离,由调用登录功能的组件负责跳转:

  1. 修改UserProvider的loginUser函数,返回登录成功状态:
const loginUser = async (username: string, password: string): Promise<boolean> => {
  try {
    const res = await loginAPI(username, password);
    if (res) {
      localStorage.setItem("token", res?.data.token);
      const userObj = {
        username: res?.data.username,
        email: res?.data.email,
      };
      localStorage.setItem("user", JSON.stringify(userObj));
      setToken(res?.data.token!);
      setUser(userObj!);
      toast.success("Login Success!");
      return true;
    }
    return false;
  } catch (e) {
    toast.warning("Server error occured");
    return false;
  }
};
  1. 在登录组件中处理导航:
import { useAuth } from 'src/context/useAuth';
import { useNavigate } from 'react-router-dom';

const LoginPage = () => {
  const { loginUser } = useAuth();
  const navigate = useNavigate();
  const [username, setUsername] = useState('');
  const [password, setPassword] = useState('');

  const handleLogin = async () => {
    const success = await loginUser(username, password);
    if (success) {
      navigate("/");
    }
  };

  // 其余登录页面逻辑...
};

方法三:确保Router包裹UserProvider(适用于手动配置react-router的场景)

如果项目中是手动引入react-router的Router组件,需要让Router包裹UserProvider:

import { BrowserRouter } from 'react-router-dom';

// 在_app.tsx的return中调整层级
return (
  <BrowserRouter>
    <UserProvider>
      <CacheProvider value={emotionCache}>
        {/* 其余组件内容 */}
      </CacheProvider>
    </UserProvider>
  </BrowserRouter>
)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 08:06:09