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

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});

问题根源

  1. 组件层级错误:useApolloClient在UserProvider外部调用,Context的作用域仅覆盖其包裹的子组件,导致钩子初始化时无法获取到UserContext的更新值。
  2. useState闭包陷阱:useState的初始化函数仅执行一次,初始时user为null,后续user更新不会触发重新初始化。
  3. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 20:00:36