Skip to content

Commit ffb179e

Browse files
authored
Better TData types when using returnPartialData or a different errorPolicy (#10766)
1 parent 174ab97 commit ffb179e

File tree

11 files changed

+427
-29
lines changed

11 files changed

+427
-29
lines changed

.changeset/sharp-trees-cough.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@apollo/client': patch
3+
---
4+
5+
More robust typings for the `data` property returned from `useSuspenseQuery` when using `returnPartialData: true` or an `errorPolicy` of `all` or `ignore`. `TData` now defaults to `unknown` instead of `any`.

.prettierignore

+2
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ src/utilities/*
3939
!src/utilities/types/
4040
src/utilities/types/*
4141
!src/utilities/types/DeepOmit.ts
42+
!src/utilities/types/DeepPartial.ts
43+
!src/utilities/types/Primitive.ts
4244
!src/utilities/common
4345
src/utilities/common/*
4446
!src/utilities/common/stripTypename.ts

package-lock.json

+13
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@
128128
"cross-fetch": "3.1.5",
129129
"eslint": "8.39.0",
130130
"eslint-plugin-testing-library": "5.10.3",
131+
"expect-type": "0.15.0",
131132
"fetch-mock": "9.11.0",
132133
"glob": "8.1.0",
133134
"graphql": "16.6.0",

src/react/hooks/__tests__/useSuspenseQuery.test.tsx

+265-13
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { ErrorBoundary } from 'react-error-boundary';
1313
import { GraphQLError } from 'graphql';
1414
import { InvariantError } from 'ts-invariant';
1515
import { equal } from '@wry/equality';
16+
import { expectTypeOf } from 'expect-type';
1617

1718
import {
1819
gql,
@@ -30,6 +31,7 @@ import {
3031
NetworkStatus,
3132
} from '../../../core';
3233
import {
34+
DeepPartial,
3335
compact,
3436
concatPagination,
3537
getMainDefinition,
@@ -234,21 +236,24 @@ function useErrorCase<TData extends ErrorCaseData>(
234236
return { query, mocks: [mock] };
235237
}
236238

237-
function useVariablesQueryCase() {
238-
const CHARACTERS = ['Spider-Man', 'Black Widow', 'Iron Man', 'Hulk'];
239+
interface VariablesCaseData {
240+
character: {
241+
id: string;
242+
name: string;
243+
};
244+
}
239245

240-
interface QueryData {
241-
character: {
242-
id: string;
243-
name: string;
244-
};
245-
}
246+
interface VariablesCaseVariables {
247+
id: string;
248+
}
246249

247-
interface QueryVariables {
248-
id: string;
249-
}
250+
function useVariablesQueryCase() {
251+
const CHARACTERS = ['Spider-Man', 'Black Widow', 'Iron Man', 'Hulk'];
250252

251-
const query: TypedDocumentNode<QueryData, QueryVariables> = gql`
253+
const query: TypedDocumentNode<
254+
VariablesCaseData,
255+
VariablesCaseVariables
256+
> = gql`
252257
query CharacterQuery($id: ID!) {
253258
character(id: $id) {
254259
id
@@ -3129,7 +3134,7 @@ describe('useSuspenseQuery', () => {
31293134
});
31303135

31313136
it('can unset a globally defined variable', async () => {
3132-
const query = gql`
3137+
const query: TypedDocumentNode<{ vars: Record<string, any> }> = gql`
31333138
query MergedVariablesQuery {
31343139
vars
31353140
}
@@ -7008,4 +7013,251 @@ describe('useSuspenseQuery', () => {
70087013
expect(todo).toHaveTextContent('Take out trash (completed)');
70097014
});
70107015
});
7016+
7017+
describe.skip('type tests', () => {
7018+
it('returns unknown when TData cannot be inferred', () => {
7019+
const query = gql`
7020+
query {
7021+
hello
7022+
}
7023+
`;
7024+
7025+
const { data } = useSuspenseQuery(query);
7026+
7027+
expectTypeOf(data).toEqualTypeOf<unknown>();
7028+
});
7029+
7030+
it('disallows wider variables type than specified', () => {
7031+
const { query } = useVariablesQueryCase();
7032+
7033+
// @ts-expect-error should not allow wider TVariables type
7034+
useSuspenseQuery(query, { variables: { id: '1', foo: 'bar' } });
7035+
});
7036+
7037+
it('returns TData in default case', () => {
7038+
const { query } = useVariablesQueryCase();
7039+
7040+
const { data: inferred } = useSuspenseQuery(query);
7041+
7042+
expectTypeOf(inferred).toEqualTypeOf<VariablesCaseData>();
7043+
expectTypeOf(inferred).not.toEqualTypeOf<VariablesCaseData | undefined>();
7044+
7045+
const { data: explicit } = useSuspenseQuery<
7046+
VariablesCaseData,
7047+
VariablesCaseVariables
7048+
>(query);
7049+
7050+
expectTypeOf(explicit).toEqualTypeOf<VariablesCaseData>();
7051+
expectTypeOf(explicit).not.toEqualTypeOf<VariablesCaseData | undefined>();
7052+
});
7053+
7054+
it('returns TData | undefined with errorPolicy: "ignore"', () => {
7055+
const { query } = useVariablesQueryCase();
7056+
7057+
const { data: inferred } = useSuspenseQuery(query, {
7058+
errorPolicy: 'ignore',
7059+
});
7060+
7061+
expectTypeOf(inferred).toEqualTypeOf<VariablesCaseData | undefined>();
7062+
expectTypeOf(inferred).not.toEqualTypeOf<VariablesCaseData>();
7063+
7064+
const { data: explicit } = useSuspenseQuery<
7065+
VariablesCaseData,
7066+
VariablesCaseVariables
7067+
>(query, { errorPolicy: 'ignore' });
7068+
7069+
expectTypeOf(explicit).toEqualTypeOf<VariablesCaseData | undefined>();
7070+
expectTypeOf(explicit).not.toEqualTypeOf<VariablesCaseData>();
7071+
});
7072+
7073+
it('returns TData | undefined with errorPolicy: "all"', () => {
7074+
const { query } = useVariablesQueryCase();
7075+
7076+
const { data: inferred } = useSuspenseQuery(query, {
7077+
errorPolicy: 'all',
7078+
});
7079+
7080+
expectTypeOf(inferred).toEqualTypeOf<VariablesCaseData | undefined>();
7081+
expectTypeOf(inferred).not.toEqualTypeOf<VariablesCaseData>();
7082+
7083+
const { data: explicit } = useSuspenseQuery<
7084+
VariablesCaseData,
7085+
VariablesCaseVariables
7086+
>(query, {
7087+
errorPolicy: 'all',
7088+
});
7089+
7090+
expectTypeOf(explicit).toEqualTypeOf<VariablesCaseData | undefined>();
7091+
expectTypeOf(explicit).not.toEqualTypeOf<VariablesCaseData>();
7092+
});
7093+
7094+
it('returns TData with errorPolicy: "none"', () => {
7095+
const { query } = useVariablesQueryCase();
7096+
7097+
const { data: inferred } = useSuspenseQuery(query, {
7098+
errorPolicy: 'none',
7099+
});
7100+
7101+
expectTypeOf(inferred).toEqualTypeOf<VariablesCaseData>();
7102+
expectTypeOf(inferred).not.toEqualTypeOf<VariablesCaseData | undefined>();
7103+
7104+
const { data: explicit } = useSuspenseQuery<
7105+
VariablesCaseData,
7106+
VariablesCaseVariables
7107+
>(query, { errorPolicy: 'none' });
7108+
7109+
expectTypeOf(explicit).toEqualTypeOf<VariablesCaseData>();
7110+
expectTypeOf(explicit).not.toEqualTypeOf<VariablesCaseData | undefined>();
7111+
});
7112+
7113+
it('returns DeepPartial<TData> with returnPartialData: true', () => {
7114+
const { query } = useVariablesQueryCase();
7115+
7116+
const { data: inferred } = useSuspenseQuery(query, {
7117+
returnPartialData: true,
7118+
});
7119+
7120+
expectTypeOf(inferred).toEqualTypeOf<DeepPartial<VariablesCaseData>>();
7121+
expectTypeOf(inferred).not.toEqualTypeOf<VariablesCaseData>();
7122+
7123+
const { data: explicit } = useSuspenseQuery<
7124+
VariablesCaseData,
7125+
VariablesCaseVariables
7126+
>(query, { returnPartialData: true });
7127+
7128+
expectTypeOf(explicit).toEqualTypeOf<DeepPartial<VariablesCaseData>>();
7129+
expectTypeOf(explicit).not.toEqualTypeOf<VariablesCaseData>();
7130+
});
7131+
7132+
it('returns TData with returnPartialData: false', () => {
7133+
const { query } = useVariablesQueryCase();
7134+
7135+
const { data: inferred } = useSuspenseQuery(query, {
7136+
returnPartialData: false,
7137+
});
7138+
7139+
expectTypeOf(inferred).toEqualTypeOf<VariablesCaseData>();
7140+
expectTypeOf(inferred).not.toEqualTypeOf<
7141+
DeepPartial<VariablesCaseData>
7142+
>();
7143+
7144+
const { data: explicit } = useSuspenseQuery<
7145+
VariablesCaseData,
7146+
VariablesCaseVariables
7147+
>(query, {
7148+
returnPartialData: false,
7149+
});
7150+
7151+
expectTypeOf(explicit).toEqualTypeOf<VariablesCaseData>();
7152+
expectTypeOf(explicit).not.toEqualTypeOf<
7153+
DeepPartial<VariablesCaseData>
7154+
>();
7155+
});
7156+
7157+
it('returns TData when passing an option that does not affect TData', () => {
7158+
const { query } = useVariablesQueryCase();
7159+
7160+
const { data: inferred } = useSuspenseQuery(query, {
7161+
fetchPolicy: 'no-cache',
7162+
});
7163+
7164+
expectTypeOf(inferred).toEqualTypeOf<VariablesCaseData>();
7165+
expectTypeOf(inferred).not.toEqualTypeOf<
7166+
DeepPartial<VariablesCaseData>
7167+
>();
7168+
7169+
const { data: explicit } = useSuspenseQuery<
7170+
VariablesCaseData,
7171+
VariablesCaseVariables
7172+
>(query, { fetchPolicy: 'no-cache' });
7173+
7174+
expectTypeOf(explicit).toEqualTypeOf<VariablesCaseData>();
7175+
expectTypeOf(explicit).not.toEqualTypeOf<
7176+
DeepPartial<VariablesCaseData>
7177+
>();
7178+
});
7179+
7180+
it('handles combinations of options', () => {
7181+
const { query } = useVariablesQueryCase();
7182+
7183+
const { data: inferredPartialDataIgnore } = useSuspenseQuery(query, {
7184+
returnPartialData: true,
7185+
errorPolicy: 'ignore',
7186+
});
7187+
7188+
expectTypeOf(inferredPartialDataIgnore).toEqualTypeOf<
7189+
DeepPartial<VariablesCaseData> | undefined
7190+
>();
7191+
expectTypeOf(
7192+
inferredPartialDataIgnore
7193+
).not.toEqualTypeOf<VariablesCaseData>();
7194+
7195+
const { data: explicitPartialDataIgnore } = useSuspenseQuery<
7196+
VariablesCaseData,
7197+
VariablesCaseVariables
7198+
>(query, {
7199+
returnPartialData: true,
7200+
errorPolicy: 'ignore',
7201+
});
7202+
7203+
expectTypeOf(explicitPartialDataIgnore).toEqualTypeOf<
7204+
DeepPartial<VariablesCaseData> | undefined
7205+
>();
7206+
expectTypeOf(
7207+
explicitPartialDataIgnore
7208+
).not.toEqualTypeOf<VariablesCaseData>();
7209+
7210+
const { data: inferredPartialDataNone } = useSuspenseQuery(query, {
7211+
returnPartialData: true,
7212+
errorPolicy: 'none',
7213+
});
7214+
7215+
expectTypeOf(inferredPartialDataNone).toEqualTypeOf<
7216+
DeepPartial<VariablesCaseData>
7217+
>();
7218+
expectTypeOf(
7219+
inferredPartialDataNone
7220+
).not.toEqualTypeOf<VariablesCaseData>();
7221+
7222+
const { data: explicitPartialDataNone } = useSuspenseQuery<
7223+
VariablesCaseData,
7224+
VariablesCaseVariables
7225+
>(query, {
7226+
returnPartialData: true,
7227+
errorPolicy: 'none',
7228+
});
7229+
7230+
expectTypeOf(explicitPartialDataNone).toEqualTypeOf<
7231+
DeepPartial<VariablesCaseData>
7232+
>();
7233+
expectTypeOf(
7234+
explicitPartialDataNone
7235+
).not.toEqualTypeOf<VariablesCaseData>();
7236+
});
7237+
7238+
it('returns correct TData type when combined options that do not affect TData', () => {
7239+
const { query } = useVariablesQueryCase();
7240+
7241+
const { data: inferred } = useSuspenseQuery(query, {
7242+
fetchPolicy: 'no-cache',
7243+
returnPartialData: true,
7244+
errorPolicy: 'none',
7245+
});
7246+
7247+
expectTypeOf(inferred).toEqualTypeOf<DeepPartial<VariablesCaseData>>();
7248+
expectTypeOf(inferred).not.toEqualTypeOf<VariablesCaseData>();
7249+
7250+
const { data: explicit } = useSuspenseQuery<
7251+
VariablesCaseData,
7252+
VariablesCaseVariables
7253+
>(query, {
7254+
fetchPolicy: 'no-cache',
7255+
returnPartialData: true,
7256+
errorPolicy: 'none',
7257+
});
7258+
7259+
expectTypeOf(explicit).toEqualTypeOf<DeepPartial<VariablesCaseData>>();
7260+
expectTypeOf(explicit).not.toEqualTypeOf<VariablesCaseData>();
7261+
});
7262+
});
70117263
});

0 commit comments

Comments
 (0)