Skip to content

Commit

Permalink
Ensure refetch, fetchMore, and subscribeToMore functions return…
Browse files Browse the repository at this point in the history
…ed by `useSuspenseQuery` are referentially stable between renders (#10656)

* Ensure refetch, fetchMore, and subscribeToMore are referentially stable as data is updated

* Add changeset
  • Loading branch information
jerelmiller authored Mar 17, 2023
1 parent ecf64f5 commit 54c4d2f
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 25 deletions.
5 changes: 5 additions & 0 deletions .changeset/silver-radios-chew.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@apollo/client': patch
---

Ensure `refetch`, `fetchMore`, and `subscribeToMore` functions returned by `useSuspenseQuery` are referentially stable between renders, even as `data` is updated.
37 changes: 37 additions & 0 deletions src/react/hooks/__tests__/useSuspenseQuery.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,43 @@ describe('useSuspenseQuery', () => {
expect(result.current).toBe(previousResult);
});

it('ensures refetch, fetchMore, and subscribeToMore are referentially stable even after result data has changed', async () => {
const { query, mocks } = useSimpleQueryCase();

const client = new ApolloClient({
link: new MockLink(mocks),
cache: new InMemoryCache(),
});

const { result } = renderSuspenseHook(() => useSuspenseQuery(query), {
client,
});

await waitFor(() => {
expect(result.current).toMatchObject({
...mocks[0].result,
error: undefined,
});
});

const previousResult = result.current;

client.writeQuery({
query,
data: { greeting: 'Updated cache greeting' },
});

await waitFor(() => {
expect(result.current.data).toEqual({
greeting: 'Updated cache greeting',
});
});

expect(result.current.fetchMore).toBe(previousResult.fetchMore);
expect(result.current.refetch).toBe(previousResult.refetch);
expect(result.current.subscribeToMore).toBe(previousResult.subscribeToMore);
});

it('enables canonical results when canonizeResults is "true"', async () => {
interface Result {
__typename: string;
Expand Down
86 changes: 61 additions & 25 deletions src/react/hooks/useSuspenseQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,26 @@ export interface UseSuspenseQueryResult<
client: ApolloClient<any>;
data: TData;
error: ApolloError | undefined;
fetchMore: ObservableQueryFields<TData, TVariables>['fetchMore'];
refetch: ObservableQueryFields<TData, TVariables>['refetch'];
subscribeToMore: ObservableQueryFields<TData, TVariables>['subscribeToMore'];
fetchMore: FetchMoreFunction<TData, TVariables>;
refetch: RefetchFunction<TData, TVariables>;
subscribeToMore: SubscribeToMoreFunction<TData, TVariables>;
}

type FetchMoreFunction<
TData,
TVariables extends OperationVariables
> = ObservableQueryFields<TData, TVariables>['fetchMore'];

type RefetchFunction<
TData,
TVariables extends OperationVariables
> = ObservableQueryFields<TData, TVariables>['refetch'];

type SubscribeToMoreFunction<
TData,
TVariables extends OperationVariables
> = ObservableQueryFields<TData, TVariables>['subscribeToMore'];

const SUPPORTED_FETCH_POLICIES: WatchQueryFetchPolicy[] = [
'cache-first',
'network-only',
Expand Down Expand Up @@ -147,34 +162,55 @@ export function useSuspenseQuery_experimental<
};
}, []);

const fetchMore: FetchMoreFunction<TData, TVariables> = useCallback(
(options) => {
const promise = observable.fetchMore(options);

suspenseCache.add(query, watchQueryOptions.variables, {
promise,
observable,
});

return promise;
},
[observable]
);

const refetch: RefetchFunction<TData, TVariables> = useCallback(
(variables) => {
const promise = observable.refetch(variables);

suspenseCache.add(query, watchQueryOptions.variables, {
promise,
observable,
});

return promise;
},
[observable]
);

const subscribeToMore: SubscribeToMoreFunction<TData, TVariables> =
useCallback((options) => observable.subscribeToMore(options), [observable]);

return useMemo(() => {
return {
client,
data: result.data,
error: errorPolicy === 'ignore' ? void 0 : toApolloError(result),
fetchMore: (options) => {
const promise = observable.fetchMore(options);

suspenseCache.add(query, watchQueryOptions.variables, {
promise,
observable,
});

return promise;
},
refetch: (variables?: Partial<TVariables>) => {
const promise = observable.refetch(variables);

suspenseCache.add(query, watchQueryOptions.variables, {
promise,
observable,
});

return promise;
},
subscribeToMore: (options) => observable.subscribeToMore(options),
fetchMore,
refetch,
subscribeToMore,
};
}, [client, result, observable, errorPolicy]);
}, [
client,
fetchMore,
refetch,
result,
observable,
errorPolicy,
subscribeToMore,
]);
}

function validateOptions(options: WatchQueryOptions) {
Expand Down

0 comments on commit 54c4d2f

Please sign in to comment.