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

update README to include PreloadQuery #303

Merged
merged 5 commits into from
May 29, 2024
Merged
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 62 additions & 4 deletions packages/experimental-nextjs-app-support/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
> This cannot be addressed from our side, but would need API changes in Next.js or React itself.
> If you do not use suspense in your application, this will not be a problem to you.

| ☑️ Apollo Client User Survey |
| :----- |
| ☑️ Apollo Client User Survey |
| :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| What do you like best about Apollo Client? What needs to be improved? Please tell us by taking a [one-minute survey](https://docs.google.com/forms/d/e/1FAIpQLSczNDXfJne3ZUOXjk9Ursm9JYvhTh1_nFTDfdq3XBAFWCzplQ/viewform?usp=pp_url&entry.1170701325=Apollo+Client&entry.204965213=Readme). Your responses will help us understand Apollo Client usage and allow us to serve you better. |

## Detailed technical breakdown
Expand Down Expand Up @@ -57,7 +57,7 @@ import {
InMemoryCache,
} from "@apollo/experimental-nextjs-app-support";

export const { getClient } = registerApolloClient(() => {
export const { getClient, query, PreloadQuery } = registerApolloClient(() => {
return new ApolloClient({
cache: new InMemoryCache(),
link: new HttpLink({
Expand All @@ -75,9 +75,13 @@ You can then use that `getClient` function in your server components:

```js
const { data } = await getClient().query({ query: userQuery });
// `query` is a shortcut for `getClient().query`
const { data } = await query({ query: userQuery });
```

### In SSR
For a description of `PreloadQuery`, see [Preloading data in RSC for usage in Client Components](#preloading-data-in-rsc-for-usage-in-client-components)

### In Client Components and streaming SSR

If you use the `app` directory, each Client Component _will_ be SSR-rendered for the initial request. So you will need to use this package.

Expand Down Expand Up @@ -154,6 +158,60 @@ export default function RootLayout({

If you want to make the most of the streaming SSR features offered by React & the Next.js App Router, consider using the [`useSuspenseQuery`](https://www.apollographql.com/docs/react/api/react/hooks-experimental/#using-usesuspensequery_experimental) and [`useFragment`](https://www.apollographql.com/docs/react/api/react/hooks-experimental/#using-usefragment_experimental) hooks.

### Preloading data in RSC for usage in Client Components

You can also preload data in RSC to populate the cache of your Client Components.
phryneas marked this conversation as resolved.
Show resolved Hide resolved

For that, follow the setup steps for both RSC and Client Components as laid out in the last two paragraphs.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
For that, follow the setup steps for both RSC and Client Components as laid out in the last two paragraphs.
For that, follow the setup steps for both RSC and Client Components as laid out in the last two sections.

Technically the first sentence in this section is a paragraph and I don't think you're meaning to reference that one, so I'd use the word "sections" here instead to avoid confusion.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
For that, follow the setup steps for both RSC and Client Components as laid out in the last two paragraphs.
As a prerequisite, follow the steps outlined in the ["In RSC"](#in-rsc) and ["In Client Components and streaming SSR"](#in-client-components-and-streaming-ssr) sections to configure your client.

Alternatively, you can reference the section names with links to really make sure we know what to follow here :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those links will be dead with the first rewording PR, so I'll choose the version without links :D


Then you can use the `PreloadQuery` component in your React Server Components:
phryneas marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
For that, follow the setup steps for both RSC and Client Components as laid out in the last two paragraphs.
Then you can use the `PreloadQuery` component in your React Server Components:
For that, follow the setup steps for both RSC and Client Components as laid out in the last two paragraphs.
Then you can use the `PreloadQuery` component in your Server Components:

The React docs tend to use "Server Components" without the word "React" when referring to them so perhaps we should do the same?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer to be clear here, as we also use "RSC" in places.


```jsx
<PreloadQuery
query={QUERY}
variables={{
foo: 1,
}}
>
<Suspense fallback={<>loading</>}>
<ClientChild />
phryneas marked this conversation as resolved.
Show resolved Hide resolved
</Suspense>
</PreloadQuery>
```

> The `Suspense` boundary here is optional.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we should explain why the Suspense boundary here is optional so we can let users know when it should be used. Is this only needed if ClientChild uses a useSuspenseQuery?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's just entirely optional. It would otherwise suspend up to the next suspense boundary, somewhere in the component tree.


This example will fetch your query in RSC, and then transport the data into your Client Component cache.
phryneas marked this conversation as resolved.
Show resolved Hide resolved
Before the child `ClientComponent` in the example renders, a "simulated network request" for this query is started in your Client Components.
phryneas marked this conversation as resolved.
Show resolved Hide resolved
That way, if you repeat the query in your Client Component using `useSuspenseQuery` (or even `useQuery`!), it will wait for the network request in your Server Component to finish instead of making it's own network request.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Part of me thinks the "simulated network request" is more of an implementation detail, and I'm not sure if we should care to mention that or not. I do like that you detail how this works in tandom with useSuspenseQuery since that is what the user cares about. Perhaps we could remove the implementation detail part of this statement and just keep to the high level overview?

Suggested change
Before the child `ClientComponent` in the example renders, a "simulated network request" for this query is started in your Client Components.
That way, if you repeat the query in your Client Component using `useSuspenseQuery` (or even `useQuery`!), it will wait for the network request in your Server Component to finish instead of making it's own network request.
Because we are using a `PreloadQuery` component wrapped around `ClientChild`, `useSuspenseQuery` will recognize this and avoid making a network request of its own. This also works for Client Components that use the `useQuery` hook as well!

(note the change assumes we show ClientChild somewhere with a useSuspenseQuery)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After rereading that, I believe it's useful knowledge for the "Caveat" section.


> Keep in mind that we recommend not to mix "client data" and "RSC data". Data fetched this way should be considerd "client data" and never be referenced in your Server Components. In fact, `PreloadQuery` will create a separate `ApolloClient` instance from the instance normally used in RSC, to prevent mixing data.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
> Keep in mind that we recommend not to mix "client data" and "RSC data". Data fetched this way should be considerd "client data" and never be referenced in your Server Components. In fact, `PreloadQuery` will create a separate `ApolloClient` instance from the instance normally used in RSC, to prevent mixing data.
> [!IMPORTANT]
> Keep in mind that we recommend not to mix "client data" and "RSC data". Data fetched this way should be considerd "client data" and never be referenced in your Server Components. In fact, `PreloadQuery` will create a separate `ApolloClient` instance from the instance normally used in RSC, to prevent mixing data.

I'd recommend an alert here to really call this out as something important. I chose IMPORTANT here, but WARNING or CAUTION also seems appropriate

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
> Keep in mind that we recommend not to mix "client data" and "RSC data". Data fetched this way should be considerd "client data" and never be referenced in your Server Components. In fact, `PreloadQuery` will create a separate `ApolloClient` instance from the instance normally used in RSC, to prevent mixing data.
> Keep in mind that we recommend not to mix "client data" and "RSC data". Data fetched this way should be considered "client data" and never be referenced in your Server Components. In fact, `PreloadQuery` will create a separate `ApolloClient` instance from the instance normally used in RSC, to prevent mixing data.

Typo

phryneas marked this conversation as resolved.
Show resolved Hide resolved

#### Usage with `useReadQuery`.

You can also use this approach in combination with `useReadQuery` on the client. For that, you can use this "render prop" approach to get a transportable `QueryRef` that you can pass down into your Client Components:
phryneas marked this conversation as resolved.
Show resolved Hide resolved

```jsx
<PreloadQuery
query={QUERY}
variables={{
foo: 1,
}}
>
{(queryRef) => (
<Suspense fallback={<>loading</>}>
<ClientChild queryRef={queryRef} />
phryneas marked this conversation as resolved.
Show resolved Hide resolved
</Suspense>
)}
</PreloadQuery>
```

Inside of `ClientChild`, you could then call `useReadQuery` with the `queryRef` prop. The `Suspense` boundary in the example is optional.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Making the change to the example code, I'd either eliminate this, or move some of this info to a comment in the code block itself.

The Suspense boundary in the example is optional.

Like before, I'd recommend explaining when/why its optional. I'm actually surprised it is optional considering useReadQuery suspends on pending requests, so if I don't know when, I'm guessing our users won't either :)


#### Caveat
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this caveat meant for PreloadQuery in general or just with the last section? I see this uses a heading level 4 which puts it at the same level as the other two sections, which means its part of the "Preloading data in RSC for usage in Client Components" section.

If this is meant for the query ref section, I'd consider removing this heading and putting the next sentence in either a block quote or NOTE alert

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It applies to both notations.


Keep in mind that this will look like a "new network request" to your Client Component, so make sure that the data you pass from your Server Components is not outdated, e.g. because of other caching layers you might be using.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm actually a little confused at what you're getting at here, mostly because some of this seems a little contradictory. You mention its a "new" network request, but yet it might suffer from being "outdated".

How would one make sure the data is not outdated and how would a user reconcile this? And is "other caching layers" referring to the Next.js fetch cache? Some more detail might be helpful here, otherwise you might consider removing this section to avoid confusion.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Example: Your client component cache has up-to-date data from a recent mutation, and you get incoming data from an RSC that is already cached for days because the Next.js fetch cache didn't update.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hope 2528e36 helps a bit


### Resetting singletons between tests.

This package uses some singleton instances on the Browser side - if you are writing tests, you must reset them between tests.
Expand Down
Loading