Next.js报Cannot read property 'useContext' of null错误解决
问题答案
先给明确结论:
- React Context的值根本不支持在函数组件/自定义Hook外部访问。
useContext是React Hook的一种,有硬性调用要求:只能在React函数组件的顶层作用域、或者自定义Hook内部调用。你在模块顶层、普通工具函数、组件渲染树之外调用的时候,React找不到对应的组件实例上下文,就会抛出你碰到的TypeError: Cannot read property 'useContext' of null错误。 - 你现在的代码有两个核心问题:一是在模块最外层直接非法调用
useContext;二是withApollo初始化ApolloClient的时机比App组件渲染早,这时候包裹内容的<Provider>还没挂载,就算把useContext挪到withApollo的回调函数里,也拿不到Context里存的值。 - 没必要硬凑在组件外面读Context,用Apollo自带的请求链路拦截器动态给请求加header,是生产环境最通用的实现方式,稳定性更高。
推荐实现代码
直接修改_app.js文件即可,其他业务逻辑不用动:
import "../styles/globals.css"; import Layout from "../components/Layout"; import { Provider, AppContext } from "../context/AppContext"; import Cookies from "js-cookie"; import withApollo from "next-with-apollo"; import { BACKEND_URL } from "../helpers"; import React, { useContext } from "react"; import { ApolloClient, ApolloProvider, InMemoryCache, createHttpLink, from } from "@apollo/client"; import { setContext } from "@apollo/client/link/context"; // 基础GraphQL请求链路 const httpLink = createHttpLink({ uri: `${BACKEND_URL}/graphql`, }); // 鉴权拦截器:每个请求发出前动态往header里塞鉴权信息 const authLink = setContext((_, { headers }) => { // 直接从Cookie读token就行,你Context里存的user信息本来就是和Cookie同步的,直接读原始数据源更稳定,没必要绕一层Context传值 const token = Cookies.get("your_token_cookie_key"); // 替换成你项目里实际存token的Cookie键名 return { headers: { ...headers, authorization: token ? `Bearer ${token}` : "", }, }; }); function App({ Component, pageProps, apollo }) { // 组件内部可以正常调用useContext拿user,不影响你之前的业务使用 const { user } = useContext(AppContext); console.log("组件内正常获取user:", user); return ( <ApolloProvider client={apollo}> <Provider> <Layout> <Component {...pageProps} /> </Layout> </Provider> </ApolloProvider> ); } export default withApollo(({ initialState }) => { return new ApolloClient({ link: from([authLink, httpLink]), cache: new InMemoryCache().restore(initialState || {}), }); })(App);
可选轻量方案(不推荐生产环境用)
如果你一定要从Context里读user来配置请求头,可以把ApolloClient实例挪到App组件内部,用useMemo包裹创建,保证useContext是在组件顶层合法调用,示例结构:
import { useMemo } from "react"; function App({ Component, pageProps }) { const { user } = useContext(AppContext); // 组件内部创建Apollo实例,这时候可以正常拿到Context值 const apolloClient = useMemo(() => { return new ApolloClient({ uri: `${BACKEND_URL}/graphql`, cache: new InMemoryCache(), headers: user?.token ? { authorization: `Bearer ${user.token}` } : {} }) }, [user]) return ( <Provider> <ApolloProvider client={apolloClient}> <Layout> <Component {...pageProps} /> </Layout> </ApolloProvider> </Provider> ) } export default App;
注意:这个方案有明显缺陷,user一旦变化就会重建整个ApolloClient实例,清空所有已经缓存的GraphQL请求数据,会额外增加不必要的请求开销,只适合小项目临时快速实现用。
内容的提问来源于stack exchange,提问作者Dilshod Sh
相关产品推荐
相关产品推荐

