如何解决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中抽离,由调用登录功能的组件负责跳转:
- 修改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; } };
- 在登录组件中处理导航:
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
相关产品推荐
相关产品推荐

