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

Add suspenseCache as a lazy hidden property on ApolloClient #11053

Merged
merged 9 commits into from
Jul 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
20 changes: 20 additions & 0 deletions .changeset/chatty-experts-fly.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
'@apollo/client': patch
---

Add `SuspenseCache` as a lazy hidden property on ApolloClient.
This means that `SuspenseCache` is now an implementation details of Apollo Client
and you no longer need to manually instantiate it and no longer need to pass it
into `ApolloProvider`.
Trying to instantiate a `SuspenseCache` instance in your code will now throw an
error.

Migration:
```diff
-import { SuspenseCache } from '@apollo/client';

-const suspenseCache = new SuspenseCache();

-<ApolloProvider client={client} suspenseCache={suspenseCache} />;
+<ApolloProvider client={client} />;
```
4 changes: 2 additions & 2 deletions .size-limit.cjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const checks = [
{
path: "dist/apollo-client.min.cjs",
limit: "37890"
limit: "37965"
},
{
path: "dist/main.cjs",
Expand All @@ -10,7 +10,7 @@ const checks = [
{
path: "dist/index.js",
import: "{ ApolloClient, InMemoryCache, HttpLink }",
limit: "32181"
limit: "31980"
},
...[
"ApolloProvider",
Expand Down
29 changes: 3 additions & 26 deletions docs/source/api/react/hooks-experimental.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,10 @@ import UseReadQueryResult from '../../../shared/useReadQuery-result.mdx';

> ⚠️ **The `useBackgroundQuery` and `useReadQuery` hooks are currently at the [beta stage](https://www.apollographql.com/docs/resources/product-launch-stages/#alpha--beta) in Apollo Client.** If you have feedback on them, please let us know via [GitHub issues](https://github.com/apollographql/apollo-client/issues/new?assignees=&labels=&template=bug.md).

Apollo Client has beta support for the `useBackgroundQuery` and `useReadQuery` hooks, which work with React's [Suspense](https://react.dev/reference/react/Suspense) feature. You can install it via `npm install @apollo/client@beta`.
Apollo Client has beta support for the `useBackgroundQuery` and `useReadQuery` hooks, which work with React's [Suspense](https://react.dev/reference/react/Suspense) feature. You can install it via `npm install @apollo/client@rc`.

### Using `useBackgroundQuery` and `useReadQuery`

Create and provide a `SuspenseCache` instance to your `ApolloProvider`.

```js
import { SuspenseCache } from '@apollo/client';

const suspenseCache = new SuspenseCache();

<ApolloProvider suspenseCache={suspenseCache} />;
```

`useBackgroundQuery` allows a user to initiate a query outside of the component that will read and render the data with `useReadQuery`, which triggers the nearest Suspense boundary while the query is pending. This means a query can be initiated higher up in the React render tree and your application can begin requesting data before the component that has a dependency on that data begins rendering.

Ensure the component tree that uses `useReadQuery` is wrapped with React's [`Suspense`](https://react.dev/reference/react/Suspense) component. Note that the `useReadQuery` hook returns only `data`, `error` and `networkStatus` (though `networkStatus` should rarely be needed). Since Suspense provides the tools to manage our application's loading states, `useReadQuery` does not return a `loading` boolean.
Expand All @@ -42,7 +32,6 @@ import { Suspense } from 'react';
import {
ApolloClient,
InMemoryCache,
SuspenseCache,
useBackgroundQuery,
useReadQuery,
} from '@apollo/client';
Expand All @@ -53,8 +42,6 @@ const query = gql`
}
`;

const suspenseCache = new SuspenseCache();

const client = new ApolloClient({
uri: "http://localhost:4000/graphql",
cache: new InMemoryCache()
Expand Down Expand Up @@ -82,7 +69,7 @@ function Parent() {

function App() {
return (
<ApolloProvider client={client} suspenseCache={suspenseCache}>
<ApolloProvider client={client}>
<ErrorBoundary fallback={<div>Error</div>}>
<Parent />
</ErrorBoundary>
Expand Down Expand Up @@ -237,20 +224,10 @@ The `useFragment` hook accepts the following options:

> ⚠️ **The `useSuspenseQuery` hook is currently at the [beta stage](https://www.apollographql.com/docs/resources/product-launch-stages/#alpha--beta) in Apollo Client.** If you have feedback on it, please let us know via [GitHub issues](https://github.com/apollographql/apollo-client/issues/new?assignees=&labels=&template=bug.md).

Apollo Client has beta support for the `useSuspenseQuery` hook, which works with React's [Suspense](https://react.dev/reference/react/Suspense) feature. You can install it via `npm install @apollo/client@beta`.
Apollo Client has beta support for the `useSuspenseQuery` hook, which works with React's [Suspense](https://react.dev/reference/react/Suspense) feature. You can install it via `npm install @apollo/client@rc`.

### Using `useSuspenseQuery`

Create and provide a `SuspenseCache` instance to your `ApolloProvider`.

```js
import { SuspenseCache } from '@apollo/client';

const suspenseCache = new SuspenseCache();

<ApolloProvider suspenseCache={suspenseCache} />;
```

Write queries using `useSuspenseQuery`. Ensure the component is wrapped with React's [`Suspense`](https://react.dev/reference/react/Suspense) component. Note that the hook returns only `data` and `error`: since Suspense provides the tools to manage our application's loading states, `useSuspenseQuery` does not return a `loading` boolean.

See the [API reference](#usesuspensequery-api) for supported options.
Expand Down
7 changes: 1 addition & 6 deletions integration-tests/next/src/app/cc/ApolloWrapper.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';
import React from 'react';
import { HttpLink, SuspenseCache } from '@apollo/client';
import { HttpLink } from '@apollo/client';
import {
ApolloNextAppProvider,
NextSSRInMemoryCache,
Expand All @@ -21,7 +21,6 @@ export function ApolloWrapper({ children }: React.PropsWithChildren<{}>) {
return (
<ApolloNextAppProvider
makeClient={makeClient}
makeSuspenseCache={makeSuspenseCache}
>
{children}
</ApolloNextAppProvider>
Expand All @@ -37,8 +36,4 @@ export function ApolloWrapper({ children }: React.PropsWithChildren<{}>) {
link: typeof window === "undefined" ? schemaLink : httpLink,
});
}

function makeSuspenseCache() {
return new SuspenseCache();
}
}
2 changes: 1 addition & 1 deletion src/react/cache/QueryReference.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export class InternalQueryReference<TData = unknown> {
public readonly key: CacheKey;
public readonly observable: ObservableQuery<TData>;

public promiseCache?: Map<any[], Promise<ApolloQueryResult<TData>>>;
public promiseCache?: Map<CacheKey, Promise<ApolloQueryResult<TData>>>;
public promise: Promise<ApolloQueryResult<TData>>;

private subscription: ObservableSubscription;
Expand Down
2 changes: 1 addition & 1 deletion src/react/cache/SuspenseCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { canUseWeakMap } from '../../utilities/index.js';
import { InternalQueryReference } from './QueryReference.js';
import type { CacheKey } from './types.js';

interface SuspenseCacheOptions {
export interface SuspenseCacheOptions {
/**
* Specifies the amount of time, in milliseconds, the suspense cache will wait
* for a suspended component to read from the suspense cache before it
Expand Down
27 changes: 27 additions & 0 deletions src/react/cache/getSuspenseCache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type { SuspenseCacheOptions } from './index.js';
import { SuspenseCache } from './SuspenseCache.js';
import type { ApolloClient } from '../../core/ApolloClient.js';

declare module '../../core/ApolloClient.js' {
interface DefaultOptions {
react?: {
suspense?: Readonly<SuspenseCacheOptions>;
};
}
}

const suspenseCacheSymbol = Symbol.for('apollo.suspenseCache');

export function getSuspenseCache(
client: ApolloClient<object> & {
[suspenseCacheSymbol]?: SuspenseCache;
}
) {
if (!client[suspenseCacheSymbol]) {
client[suspenseCacheSymbol] = new SuspenseCache(
client.defaultOptions.react?.suspense
);
}

return client[suspenseCacheSymbol];
}
28 changes: 27 additions & 1 deletion src/react/cache/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,27 @@
export { SuspenseCache } from './SuspenseCache.js';
export type { SuspenseCacheOptions } from './SuspenseCache.js';
export { getSuspenseCache } from './getSuspenseCache.js';

import { SuspenseCache as RealSuspenseCache } from './SuspenseCache.js';

// TODO: remove export with release 3.8
// replace with
// export type { SuspenseCache } from './SuspenseCache.js';
/**
Copy link
Member Author

Choose a reason for hiding this comment

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

In the future, there will not be any export of the SuspenseCache class any more, not even Apollo-Client-internally.
Instead, we export the getSuspenseCache (only for internal use, so it should not end up in any public entry points!) function.

* @deprecated
* It is no longer necessary to create a `SuspenseCache` instance and pass it into the `ApolloProvider`.
* Please remove this code from your application.
*
* This export will be removed with the final 3.8 release.
*/
export class SuspenseCache extends RealSuspenseCache {
constructor(){
super();
// throwing an error here instead of using invariant - we do not want this error
// message to be link-ified, but to directly show up as bold as possible
throw new Error(
'It is no longer necessary to create a `SuspenseCache` instance and pass it into the `ApolloProvider`.\n' +
'Please remove this code from your application. \n\n' +
'This export will be removed with the final 3.8 release.'
);
}
}
4 changes: 3 additions & 1 deletion src/react/cache/types.ts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export type CacheKey = any[];
import type { DocumentNode } from "graphql";

export type CacheKey = [query: DocumentNode, stringifiedVariables: string, ...queryKey: any[]];
2 changes: 0 additions & 2 deletions src/react/context/ApolloContext.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import * as React from 'react';
import type { ApolloClient } from '../../core/index.js';
import { canUseSymbol } from '../../utilities/index.js';
import type { SuspenseCache } from '../cache/index.js';
import type { RenderPromises } from '../ssr/index.js';
import { global, invariant } from '../../utilities/globals/index.js';

export interface ApolloContextValue {
client?: ApolloClient<object>;
renderPromises?: RenderPromises;
suspenseCache?: SuspenseCache;
}

declare global {
Expand Down
6 changes: 1 addition & 5 deletions src/react/context/ApolloProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,14 @@ import * as React from 'react';

import type { ApolloClient } from '../../core/index.js';
import { getApolloContext } from './ApolloContext.js';
import type { SuspenseCache } from '../cache/index.js';

export interface ApolloProviderProps<TCache> {
client: ApolloClient<TCache>;
suspenseCache?: SuspenseCache;
children: React.ReactNode | React.ReactNode[] | null;
}

export const ApolloProvider: React.FC<ApolloProviderProps<any>> = ({
client,
suspenseCache,
children,
}) => {
const ApolloContext = getApolloContext();
Expand All @@ -24,9 +21,8 @@ export const ApolloProvider: React.FC<ApolloProviderProps<any>> = ({
return {
...parentContext,
client: client || parentContext.client,
suspenseCache: suspenseCache || parentContext.suspenseCache,
};
}, [parentContext, client, suspenseCache]);
}, [parentContext, client]);

invariant(
context.client,
Expand Down
14 changes: 0 additions & 14 deletions src/react/context/__tests__/ApolloProvider.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { ApolloClient } from '../../../core';
import { InMemoryCache as Cache } from '../../../cache';
import { ApolloProvider, ApolloProviderProps } from '../ApolloProvider';
import { ApolloContextValue, getApolloContext } from '../ApolloContext';
import { SuspenseCache } from '../../cache';

describe('<ApolloProvider /> Component', () => {
const client = new ApolloClient({
Expand All @@ -19,9 +18,6 @@ describe('<ApolloProvider /> Component', () => {
link: new ApolloLink((o, f) => (f ? f(o) : null)),
});

const suspenseCache = new SuspenseCache();
const anotherSuspenseCache = new SuspenseCache();

it('should render children components', () => {
render(
<ApolloProvider client={client}>
Expand Down Expand Up @@ -122,16 +118,6 @@ describe('<ApolloProvider /> Component', () => {
]
>([
['client', { client }, { client: anotherClient }],
[
'suspenseCache',
{ client, suspenseCache },
{ client, suspenseCache: anotherSuspenseCache },
],
[
'suspenseCache and client',
{ client, suspenseCache },
{ suspenseCache: anotherSuspenseCache, client: anotherClient },
],
])('context value stability, %s prop', (prop, value, childValue) => {
it(`should not recreate the context value if the ${prop} prop didn't change`, () => {
let lastContext: ApolloContextValue | undefined;
Expand Down