React问题:useContext的user值在Apollo Client钩子中无法更新
问题:Next.js中UserProvider的user无法传递给Apollo Client自定义钩子
我有一个处理Next.js应用认证的UserProvider,在UI组件中能正常访问user值,但在创建Apollo Client的自定义钩子中,user始终为null。
相关代码
UserProvider实现
export function UserProvider({ children }) { const [user, setUser] = useState(null); const [token, setToken] = useState(''); const [userLoading, setUserLoading] = useState(true); const router = useRouter(); const { data, error } = useSWR('/api/user', fetchUser); const publicRoutes = [ '/', '/login', '/docs', ]; const isPublicRoute = publicRoutes.includes(router.pathname); // Sets user data if the user is authenticated useEffect(() => { if (data?.user) { console.log('[UserProvider] user retrieved'); setUser(data.user); console.log('[UserProvider] user data set', user); setUserLoading(false); } else if (data && !data.user && !isPublicRoute) { setUser(null); setUserLoading(false); console.log('[UserProvider] user not retrieved'); if (typeof window !== 'undefined') { router.push('/login'); } } else if (error) { setUserLoading(false); console.error('[UserProvider] error', error); if (typeof window !== 'undefined') { router.push('/login'); } } }, [data, error]); return ( <UserContext.Provider value={{ user, userLoading, }} > {children} </UserContext.Provider> ); }
Apollo Client自定义钩子
const useApolloClient = () => { const { user, userLoading } = useContext(UserContext); // Initiate client const [client, setClient] = useState(() => { return new ApolloClient({ link: new HttpLink({ uri: '/graphql', }), headers: { 'content-type': 'application/json', Authorization: user?.token ? `Bearer ${user.token}` : '', }, cache: new InMemoryCache(), }); }); useEffect(() => { if (user) { const httpLink = createHttpLink({ uri: '/graphql', }); const errorLink = onError(({ graphQLErrors, networkError }) => { if (graphQLErrors) graphQLErrors.forEach(({ message, locations, path }) => console.log( `[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`, ), ); if (networkError) console.error(`[Network error]: ${networkError}`); }); const authLink = setContext((_, { headers }) => { // Ensure user data is available and valid if (user) { console.log('[ApolloClient] Token ', user.token); } if (!user) { console.error('[ApolloClient] User data is incomplete or not available.'); return { headers }; } return { headers: { ...headers, 'content-type': 'application/json', Authorization: user?.token ? `Bearer ${user.token}` : '', }, }; }); const apolloClient = new ApolloClient({ link: authLink.concat(errorLink).concat(httpLink), cache: new InMemoryCache(), }); setClient(apolloClient); } }, [user, userLoading]); return client; }; export default useApolloClient;
App组件结构
const App = ({ Component, pageProps }) => { const router = useRouter(); const client = useApolloClient(); return ( <UserProvider> <ApolloProvider client={client}> <ThemeProvider theme={theme}> <StoreProvider store={store}> <GlobalStyle /> <Component {...pageProps} /> </StoreProvider> </ThemeProvider> </ApolloProvider> </UserProvider> ); };
UserContext初始化
export const UserContext = createContext({ login: null, user: null, token: null, userLoading: true, setUser: null});
问题根源
- 组件层级错误:
useApolloClient在UserProvider外部调用,Context的作用域仅覆盖其包裹的子组件,导致钩子初始化时无法获取到UserContext的更新值。 - useState闭包陷阱:useState的初始化函数仅执行一次,初始时user为
null,后续user更新不会触发重新初始化。 - setContext依赖闭包:authLink中的setContext依赖初始化时的user闭包,无法获取最新的user值。
修复方案
1. 调整App组件层级
将useApolloClient移到UserProvider内部调用,确保在Context作用域内:
const App = ({ Component, pageProps }) => { return ( <UserProvider> <ApolloWrapper> <ThemeProvider theme={theme}> <StoreProvider store={store}> <GlobalStyle /> <Component {...pageProps} /> </StoreProvider> </ThemeProvider> </ApolloWrapper> </UserProvider> ); }; // 新增Wrapper确保在UserContext作用域内创建client const ApolloWrapper = ({ children }) => { const client = useApolloClient(); return <ApolloProvider client={client}>{children}</ApolloProvider>; };
2. 优化useApolloClient逻辑
去掉useState初始化的依赖,利用setContext的动态特性,每次请求时获取最新user:
const useApolloClient = () => { const { user } = useContext(UserContext); const [client, setClient] = useState(null); useEffect(() => { const httpLink = createHttpLink({ uri: '/graphql' }); const errorLink = onError(({ graphQLErrors, networkError }) => { if (graphQLErrors) { graphQLErrors.forEach(({ message, locations, path }) => console.log(`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`) ); } if (networkError) console.error(`[Network error]: ${networkError}`); }); // 动态获取最新user,不受闭包限制 const authLink = setContext(() => { return { headers: { 'content-type': 'application/json', Authorization: user?.token ? `Bearer ${user.token}` : '' } }; }); const newClient = new ApolloClient({ link: authLink.concat(errorLink).concat(httpLink), cache: new InMemoryCache() }); setClient(newClient); }, [user]); return client; };
3. 修正UserProvider的日志逻辑
setUser是异步操作,直接打印user会输出旧值,改为监听user变化:
// 原useEffect保留状态更新 useEffect(() => { if (data?.user) { console.log('[UserProvider] user retrieved'); setUser(data.user); setUserLoading(false); } // ...其他逻辑 }, [data, error]); // 新增监听user变化的useEffect打印最新值 useEffect(() => { if (user) { console.log('[UserProvider] user data set', user); } }, [user]);
内容的提问来源于stack exchange,提问作者pandafiesta
相关产品推荐
相关产品推荐

