apollographql / apollo-client-nextjs

Apollo Client support for the Next.js App Router
https://www.npmjs.com/package/@apollo/experimental-nextjs-app-support
MIT License
358 stars 25 forks source link

registerApolloClient is not exported error #280

Closed jnovak-SM2Dev closed 3 weeks ago

jnovak-SM2Dev commented 4 weeks ago

When I try to use getClient().query I get the following error:

Attempted import error: 'registerApolloClient' is not exported from '@apollo/experimental-nextjs-app-support/rsc' (imported as 'registerApolloClient').

Any thoughts on what could cause this issue?

The full call i'm using:

const { data } = await getClient().query({
  query: loginMutation,
  variables: {
    loginInput: {
      email: credentials.email,
      password: credentials.password,
    },
  },
});

Next: 14.1.3 @apollo/client: 3.9.11 @apollo/experimental-nextjs-app-support: 0.10.0

phryneas commented 4 weeks ago

You would get that error when trying to import registerApolloClient (or a file that, around a hundred corners, imports from a file that imports registerApolloClient) from a "use client" file.

Are you sure you are only referencing getClient from Server Components? Do you maybe use barrel-exports like index.js files that combine Server Component and Client Component exports in a single file?

jnovak-SM2Dev commented 4 weeks ago

For this specific instance i'm trying to use it in the auth.ts Credentials call from auth.js. I believe this is a server file, but it's a bit confusing what is and isn't at this point. What is the proper way to make a call in a client component without state?

phryneas commented 4 weeks ago

I'm not sure what you mean by "without state", but in Client Components you'd use the hooks.

jnovak-SM2Dev commented 4 weeks ago

What hooks? The docs don't mention anything about the client side hooks for some reason.

jnovak-SM2Dev commented 4 weeks ago

I assume it's just useQuery and calls from the old version of apollo? How can I await a client call in an async function?

jnovak-SM2Dev commented 4 weeks ago

To better explain, in an older project with normal apollo and the pages router I had to do something like this:

const client = initializeApollo();

const { data } = await client.mutate({
  mutation: loginQuery,
  variables: {
    input: {
      provider: "local",
      identifier: email,
      password: password,
    },
});

How would I handle something like this with this version?

jnovak-SM2Dev commented 3 weeks ago

I made an example project showing the error. Notes are in the Readme.

https://github.com/jnovak-SM2Dev/next-apollo-sample

Note: I am talking with the Auth.js guys in discord as well. Not sure who's end the issue is on.

Gwened commented 3 weeks ago

Had the same issue on Next.js, here's what I found out.

Some of the hooks are documented here: https://www.apollographql.com/docs/react/data/queries

But if you don't need useLazyQuery() or useMutation() to update your React components afterwards, there's also useApolloClient(). Use it in the component body and pass the result to the function that needs to perform some async action:

function MyComponent() {
   const client = useApolloClient();
   ...
   const handleMyCallback = (inputs) => {
      client.mutation({ ...
}

Not sure it it's necessary but I also made sure that component only run client-side using this technique

phryneas commented 3 weeks ago

I have to admit that I'm still struggling a bit about what exactly @jnovak-SM2Dev wants to do here.

I'll lay out our recommendations:

I do not know how auth.js plays into all of that, as I'm not familiar with them.

Generally, apart from the Cookies/Auth part, you'd just write your code like you always would with the modern useSuspenseQuery/useBackgroundQuery hooks and this library takes care about any SSR data transport for you automatically.

jnovak-SM2Dev commented 3 weeks ago

I am using a login mutation to get a JWT token. I then use auth.js to manage the session. The issue is for some reason using getClient in the credentials provider (which I was told is server side) causes the error about registerApolloClient.

phryneas commented 3 weeks ago

Looking at your repository, you are doing so in a Route Handler?

Those don't really need any additional treatment as long as you create a new Apollo Client instance every time your route handler is called.

registerApolloClient is currently only targeting "React Server Component" - in Next.js there are a lot of different "Server" things (React Server Components, Server Side Rendering, Server Actions, Middleware, Route Handlers), not all of them need to go through registerApolloClient.

We will add support for more of those as soon as we identify use cases that give users additional benefit, but right now just calling const client = makeClient() in your route handler without registerApolloClient is good enough and we can't provide any benefit on top.

jnovak-SM2Dev commented 3 weeks ago

@phryneas That makes sense. When I try makeClient() it throws different errors though.

Error:

TypeError: _apollo_experimental_nextjs_app_support_ssr__WEBPACK_IMPORTED_MODULE_0__.NextSSRInMemoryCache is not a constructor

Is this how i'm supposed to setup makeClient()?

import { ApolloLink, HttpLink } from "@apollo/client";
import {
  NextSSRApolloClient,
  NextSSRInMemoryCache,
  SSRMultipartLink,
} from "@apollo/experimental-nextjs-app-support/ssr";

export function makeClient() {
  const httpLink = new HttpLink({
    // this needs to be an absolute url, as relative urls cannot be used in SSR
    uri: process.env.API_URL,
    // 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" }}});
  });

  return new NextSSRApolloClient({
    // use the `NextSSRInMemoryCache`, not the normal `InMemoryCache`
    cache: new NextSSRInMemoryCache(),
    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,
            }),
            httpLink,
          ])
        : httpLink,
  });
}

Sorry, it's confusing with all the client, server, makeClient, getClient, bop-it, twist-it, turn-it, style stuff I need to do. At the end of the day I just want to login the user and get a token lol.

jnovak-SM2Dev commented 3 weeks ago

If I add use client to makeClient, it throws another error as well.

TypeError: (0 , _makeClient__WEBPACK_IMPORTED_MODULE_2__.makeClient) is not a function
phryneas commented 3 weeks ago

Yeah, unfortunately Next.js has about 6 different scenarios in which your JavaScript code can run.

With makeClient I mean that you just create a function that creates an ApolloClient instance of your, not the prop passed into ApolloNextAppProvider.

You shouldn't be using NextSSRInMemoryCache or NextSSRApolloClient outside of Client Components. (React Components in files with "use client" or imported by such files).

Generally, your route handler has nothing to do with React at all (Next runs it without React!), so you don't need to pull this package into it, as this package is meant for interaction with React Server Components and React Client Components, not any non-React functionality.

jnovak-SM2Dev commented 3 weeks ago

@phryneas That worked!! Thanks!!

Yeah all this server/client nonsense is a bit annoying. I wish there was an easy way to make it so the end user just needs to call getClient anywhere and it just works.

Here's the code I used in case anyone else needs it.

import { ApolloClient, HttpLink, InMemoryCache } from "@apollo/client";

export function makeClient() {
  const httpLink = new HttpLink({
    uri: process.env.API_URL,
    fetchOptions: { cache: "no-store" },
  });
  return new ApolloClient({
    cache: new InMemoryCache(),
    link: httpLink,
  });
}

And in the auth.ts or wherever you need it you can just do:

const client = makeClient();
const { data } = await client.mutate({
  mutation: loginMutation,
  variables: {
    loginInput: {
      email: credentials.email as string,
      password: credentials.password as string,
    },
  },
});
github-actions[bot] commented 3 weeks ago

Do you have any feedback for the maintainers? Please tell us by taking a one-minute survey. Your responses will help us understand Apollo Client usage and allow us to serve you better.