Skip to content

Commit

Permalink
ApolloProvider: ensure context value stability (#10869)
Browse files Browse the repository at this point in the history
* add failing tests

* ApolloProvider: ensure context value stability

fixes #7626

* adjust bundlesize

* Update src/react/context/ApolloProvider.tsx

Co-authored-by: Jerel Miller <jerelmiller@gmail.com>

* suggestions from code review

---------

Co-authored-by: Jerel Miller <jerelmiller@gmail.com>
  • Loading branch information
phryneas and jerelmiller authored May 16, 2023
1 parent f402485 commit ba1d061
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 25 deletions.
5 changes: 5 additions & 0 deletions .changeset/dull-snakes-turn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@apollo/client': patch
---

Ensure Context value stability when rerendering ApolloProvider with the same `client` and/or `suspenseCache` prop
9 changes: 9 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,15 @@ src/*
!src/react/
src/react/*


# Allow src/react/cache/ApolloProvider
!src/react/context/
src/react/context/*
!src/react/context/ApolloProvider.tsx
!src/react/context/__tests__/
src/react/context/__tests__/*
!src/react/context/__tests__/ApolloProvider.test.tsx

# Allow src/react/cache
!src/react/cache/

Expand Down
38 changes: 17 additions & 21 deletions src/react/context/ApolloProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,29 +18,25 @@ export const ApolloProvider: React.FC<ApolloProviderProps<any>> = ({
children
}) => {
const ApolloContext = getApolloContext();
return (
<ApolloContext.Consumer>
{(context: any = {}) => {
if (client && context.client !== client) {
context = Object.assign({}, context, { client });
}
const parentContext = React.useContext(ApolloContext);

if (suspenseCache) {
context = Object.assign({}, context, { suspenseCache });
}
const context = React.useMemo(() => {
return {
...parentContext,
client: client || parentContext.client,
suspenseCache: suspenseCache || parentContext.suspenseCache
}
}, [parentContext, client, suspenseCache]);

invariant(
context.client,
'ApolloProvider was not passed a client instance. Make ' +
'sure you pass in your client via the "client" prop.'
);
invariant(
context.client,
'ApolloProvider was not passed a client instance. Make ' +
'sure you pass in your client via the "client" prop.'
);

return (
<ApolloContext.Provider value={context}>
{children}
</ApolloContext.Provider>
);
}}
</ApolloContext.Consumer>
return (
<ApolloContext.Provider value={context}>
{children}
</ApolloContext.Provider>
);
};
91 changes: 87 additions & 4 deletions src/react/context/__tests__/ApolloProvider.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,24 @@ import { render, screen } from '@testing-library/react';
import { ApolloLink } from '../../../link/core';
import { ApolloClient } from '../../../core';
import { InMemoryCache as Cache } from '../../../cache';
import { ApolloProvider } from '../ApolloProvider';
import { getApolloContext } from '../ApolloContext';
import { ApolloProvider, ApolloProviderProps } from '../ApolloProvider';
import { ApolloContextValue, getApolloContext } from '../ApolloContext';
import { SuspenseCache } from '../../cache';

describe('<ApolloProvider /> Component', () => {
const client = new ApolloClient({
cache: new Cache(),
link: new ApolloLink((o, f) => (f ? f(o) : null))
link: new ApolloLink((o, f) => (f ? f(o) : null)),
});

const anotherClient = new ApolloClient({
cache: new Cache(),
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 @@ -95,7 +104,7 @@ describe('<ApolloProvider /> Component', () => {

const newClient = new ApolloClient({
cache: new Cache(),
link: new ApolloLink((o, f) => (f ? f(o) : null))
link: new ApolloLink((o, f) => (f ? f(o) : null)),
});
clientToCheck = newClient;
rerender(
Expand All @@ -104,4 +113,78 @@ describe('<ApolloProvider /> Component', () => {
</ApolloProvider>
);
});

describe.each<
[
string,
Omit<ApolloProviderProps<any>, 'children'>,
Omit<ApolloProviderProps<any>, 'children'>
]
>([
['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;

const TestChild = () => {
lastContext = useContext(getApolloContext());
return null;
};

const { rerender } = render(
<ApolloProvider {...value}>
<TestChild />
</ApolloProvider>
);

const firstContextValue = lastContext;

rerender(
<ApolloProvider {...value}>
<TestChild />
</ApolloProvider>
);

expect(lastContext).toBe(firstContextValue);
});

it(`should not recreate the context if the parent context value differs, but the ${prop} prop didn't change`, () => {
let lastContext: ApolloContextValue | undefined;

const TestChild = () => {
lastContext = useContext(getApolloContext());
return null;
};

const { rerender } = render(
<ApolloProvider {...value}>
<ApolloProvider {...childValue}>
<TestChild />
</ApolloProvider>
</ApolloProvider>
);

const firstContextValue = lastContext;

rerender(
<ApolloProvider {...value}>
<ApolloProvider {...childValue}>
<TestChild />
</ApolloProvider>
</ApolloProvider>
);

expect(lastContext).toBe(firstContextValue);
});
});
});

0 comments on commit ba1d061

Please sign in to comment.