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

Memoize Apollo Client #11699

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Changes from all 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
92 changes: 59 additions & 33 deletions packages/web/src/apollo/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,9 @@ export type GraphQLClientConfigProp = Omit<
link?: ApolloLink | RedwoodApolloLinkFactory
}

/** Local variable to keep track of current ApolloClient instance. */
let _client: ApolloClient<unknown> | undefined

const ApolloProviderWithFetchConfig: React.FunctionComponent<{
config: Omit<GraphQLClientConfigProp, 'cacheConfig' | 'cache'> & {
cache: ApolloCache<unknown>
Expand Down Expand Up @@ -167,10 +170,10 @@ const ApolloProviderWithFetchConfig: React.FunctionComponent<{
mostRecentResponse?: any
}

const data = {
const data: ApolloRequestData = {
mostRecentRequest: undefined,
mostRecentResponse: undefined,
} as ApolloRequestData
}

const updateDataApolloLink = new ApolloLink((operation, forward) => {
const { operationName, query, variables } = operation
Expand Down Expand Up @@ -219,7 +222,11 @@ const ApolloProviderWithFetchConfig: React.FunctionComponent<{
return forward(operation)
})

const { httpLinkConfig, link: redwoodApolloLink, ...rest } = config ?? {}
const {
httpLinkConfig,
link: redwoodApolloLink,
...otherClientConfig
} = React.useMemo(() => config ?? {}, [config])

// A terminating link. Apollo Client uses this to send GraphQL operations to a server over HTTP.
// See https://www.apollographql.com/docs/react/api/link/introduction/#the-terminating-link.
Expand Down Expand Up @@ -294,26 +301,41 @@ const ApolloProviderWithFetchConfig: React.FunctionComponent<{
link = link(redwoodApolloLinks)
}

const client = new ApolloClient({
// Default options for every Cell. Better to specify them here than in `beforeQuery` where it's too easy to overwrite them.
// See https://www.apollographql.com/docs/react/api/core/ApolloClient/#example-defaultoptions-object.
defaultOptions: {
watchQuery: {
// The `fetchPolicy` we expect:
//
// > Apollo Client executes the full query against both the cache and your GraphQL server.
// > The query automatically updates if the result of the server-side query modifies cached fields.
//
// See https://www.apollographql.com/docs/react/data/queries/#cache-and-network.
fetchPolicy: 'cache-and-network',
// So that Cells rerender when refetching.
// See https://www.apollographql.com/docs/react/data/queries/#inspecting-loading-states.
notifyOnNetworkStatusChange: true,
const client = React.useMemo(() => {
// If we have a client instance, stop it before creating a new one.
if (_client) {
_client.stop()
}

_client = new ApolloClient({
// Default options for every Cell. Better to specify them here than in `beforeQuery` where it's too easy to overwrite them.
// See https://www.apollographql.com/docs/react/api/core/ApolloClient/#example-defaultoptions-object.
defaultOptions: {
watchQuery: {
// The `fetchPolicy` we expect:
//
// > Apollo Client executes the full query against both the cache and your GraphQL server.
// > The query automatically updates if the result of the server-side query modifies cached fields.
//
// See https://www.apollographql.com/docs/react/data/queries/#cache-and-network.
fetchPolicy: 'cache-and-network',
// So that Cells rerender when refetching.
// See https://www.apollographql.com/docs/react/data/queries/#inspecting-loading-states.
notifyOnNetworkStatusChange: true,
},
},
},
link,
...rest,
})
link,
...otherClientConfig,
})

return _client
// eslint-disable-next-line react-hooks/exhaustive-deps -- `link` is not stable, we use `useEffect` below to update it.
}, [otherClientConfig])

// Update the link when it changes (e.g. every re-render of this provider since `link` is currently unstable).
React.useEffect(() => {
client.setLink(link)
}, [client, link])

const extendErrorAndRethrow = (error: any, _errorInfo: React.ErrorInfo) => {
error['mostRecentRequest'] = data.mostRecentRequest
Expand Down Expand Up @@ -360,26 +382,30 @@ export const RedwoodApolloProvider: React.FunctionComponent<{
logLevel = 'debug',
children,
}) => {
// Since Apollo Client gets re-instantiated on auth changes,
// we have to instantiate `InMemoryCache` here, so that it doesn't get wiped.
const { cacheConfig, ...config } = graphQLClientConfig ?? {}

// Auto register fragments
if (fragments) {
fragmentRegistry.register(...fragments)
}

const cache = new InMemoryCache({
fragments: fragmentRegistry,
possibleTypes: cacheConfig?.possibleTypes,
...cacheConfig,
}).restore(globalThis?.__REDWOOD__APOLLO_STATE ?? {})
const apolloConfig = React.useMemo(() => {
// Since Apollo Client gets re-instantiated on auth changes,
// we have to instantiate `InMemoryCache` here, so that it doesn't get wiped.
const { cacheConfig, ...config } = graphQLClientConfig ?? {}

const cache = new InMemoryCache({
fragments: fragmentRegistry,
possibleTypes: cacheConfig?.possibleTypes,
...cacheConfig,
}).restore(globalThis?.__REDWOOD__APOLLO_STATE ?? {})

// This order so that the user can still completely overwrite the cache.
return { cache, ...config }
}, [graphQLClientConfig])

return (
<FetchConfigProvider useAuth={useAuth}>
<ApolloProviderWithFetchConfig
// This order so that the user can still completely overwrite the cache.
config={{ cache, ...config }}
config={apolloConfig}
useAuth={useAuth}
logLevel={logLevel}
>
Expand Down
Loading