Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Error: useTransportValue must be used within a streaming-specific ApolloProvider #411

Open
MorningK opened this issue Dec 19, 2024 · 3 comments

Comments

@MorningK
Copy link

sometimes it was thrown by useSuspenseQuery
image

ApolloWrapper component file

'use client';

import { ApolloLink, HttpLink, split } from '@apollo/client';
import { setContext } from '@apollo/client/link/context';
import { GraphQLWsLink } from '@apollo/client/link/subscriptions';
import { getMainDefinition } from '@apollo/client/utilities';
import {
  ApolloNextAppProvider,
  InMemoryCache,
  ApolloClient,
  SSRMultipartLink,
} from '@apollo/experimental-nextjs-app-support';
import { createClient } from 'graphql-ws';
import React from 'react';
import { fragmentRegistry } from '@/graphql/query/fragment';
import { ORGANIZATION_ID, TOKEN } from '@/lib/authenticate/logged';

// have a function to create a client for you
function makeClient(cookie: {
  token: string | null | undefined;
  organizationId: string | null | undefined;
}) {
  const httpLink = new HttpLink({
    // this needs to be an absolute url, as relative urls cannot be used in SSR
    uri: process.env.NEXT_PUBLIC_GRAPHQL_ENDPOINT,
    // you can disable result caching here if you want to
    // (this does not work if you are rendering your page with `export const dynamic = "force-static"`)
    fetchOptions: { cache: 'no-store' },
    // you can override the default `fetchOptions` on a per query basis
    // via the `context` property on the options passed as a second argument
    // to an Apollo Client data fetching hook, e.g.:
    // const { data } = useSuspenseQuery(MY_QUERY, { context: { fetchOptions: { cache: "force-cache" }}});
  });
  const wsLink = new GraphQLWsLink(
    createClient({
      url: process.env.NEXT_PUBLIC_GRAPHQL_WS_ENDPOINT as string,
      connectionParams() {
        return {
          Authorization: `Bearer ${window?.localStorage.getItem(TOKEN)}`,
          'Org-Id': window?.localStorage.getItem(ORGANIZATION_ID),
        };
      },
    }),
  );
  const authLink = setContext((_, { headers }) => {
    const token =
      typeof window === 'undefined'
        ? cookie.token
        : window.localStorage.getItem(TOKEN);
    const organizationId =
      typeof window === 'undefined'
        ? cookie.organizationId
        : window.localStorage.getItem(ORGANIZATION_ID);
    const customHeaders: Record<string, string> = {};
    if (token) {
      customHeaders['Authorization'] = `Bearer ${token}`;
    }
    if (organizationId) {
      customHeaders['Org-Id'] = organizationId;
    }
    return {
      headers: {
        ...headers,
        ...customHeaders,
      },
    };
  });
  // The split function takes three parameters:
  //
  // * A function that's called for each operation to execute
  // * The Link to use for an operation if the function returns a "truthy" value
  // * The Link to use for an operation if the function returns a "falsy" value
  const splitLink = split(
    ({ query }) => {
      const definition = getMainDefinition(query);
      return (
        definition.kind === 'OperationDefinition' &&
        definition.operation === 'subscription'
      );
    },
    wsLink,
    ApolloLink.from([authLink, httpLink] as ApolloLink[]),
  );
  return new ApolloClient({
    cache: new InMemoryCache({
      fragments: fragmentRegistry,
    }),
    link:
      typeof window === 'undefined'
        ? ApolloLink.from([
            // in a SSR environment, if you use multipart features like
            // @defer, you need to decide how to handle these.
            // This strips all interfaces with a `@defer` directive from your queries.
            new SSRMultipartLink({
              stripDefer: true,
            }),
            authLink,
            httpLink,
          ])
        : splitLink,
  });
}

// you need to create a component to wrap your app in
export function ApolloWrapper({
  children,
  token,
  organizationId,
}: React.PropsWithChildren<{
  token: string | null | undefined;
  organizationId: string | null | undefined;
}>) {
  return (
    <ApolloNextAppProvider
      makeClient={() => makeClient({ token, organizationId })}
    >
      {children}
    </ApolloNextAppProvider>
  );
}

versions

 "@apollo/client": "^3.12.3",
 "@apollo/experimental-nextjs-app-support": "^0.11.7",
 "react": "^18",
 "react-dom": "^18",
 "next": "14.2.20",

What went wrong?

@phryneas
Copy link
Member

That would indicate that you are calling useSuspenseQuery outside the child tree of ApolloWrapper - these two need React context to communicate, so everything that would break React context would break this.

Apart from the obvious "not a child" problem, things that come to mind include hot module reloading or bundling a dependency twice - or using a different React renderer that's not passing over context.

@MorningK
Copy link
Author

i was using ApolloWrapper in nextjs root layout file as child of body, so it would not be "not a child" problem ? this error was thrown in dev env, hot module reloading looks like it might be the cause ?

<html lang="zh">
      <body className={inter.className}>
          <ApolloWrapper token={token} organizationId={organizationId}>
            <AntdRegistry>
              <ConfigProvider locale={zhCN}>
                <StyleProvider>
                  <App>{children}</App>
                </StyleProvider>
              </ConfigProvider>
            </AntdRegistry>
          </ApolloWrapper>
      </body>
    </html>

@phryneas
Copy link
Member

Usage seems right here, so it could be a hot reloading problem. I can't really think about anything else - this mechanism is at it's core React Context and if you see this message, it's just an indicator that something has gone wrong with that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants