Skip to content

Commit fc00927

Browse files
fix(angular-query-experimental): allow returning undefined in initialData function
Fix incorrect types that disallowed that. This is necessary because the initialData function can typically read cache synchronously and need to be able to signal to the query if there is a cache miss and an actual fetch needs to be deployed. No breaking changes, any code that worked before should work now as well.
1 parent 7368bd0 commit fc00927

File tree

2 files changed

+112
-88
lines changed

2 files changed

+112
-88
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,127 +1,146 @@
1-
import { assertType, describe, expectTypeOf } from 'vitest'
2-
import { QueryClient } from '@tanstack/query-core'
3-
import { dataTagSymbol } from '@tanstack/query-core'
4-
import { queryOptions } from '../query-options'
5-
import { injectQuery } from '../inject-query'
6-
import type { Signal } from '@angular/core'
7-
8-
describe('queryOptions', () => {
9-
test('should not allow excess properties', () => {
1+
import { assertType, describe, expectTypeOf } from "vitest";
2+
import { QueryClient } from "@tanstack/query-core";
3+
import { dataTagSymbol } from "@tanstack/query-core";
4+
import { queryOptions } from "../query-options";
5+
import { injectQuery } from "../inject-query";
6+
import type { Signal } from "@angular/core";
7+
8+
describe("queryOptions", () => {
9+
test("should not allow excess properties", () => {
1010
return queryOptions({
11-
queryKey: ['key'],
11+
queryKey: ["key"],
1212
queryFn: () => Promise.resolve(5),
1313
// @ts-expect-error this is a good error, because stallTime does not exist!
1414
stallTime: 1000,
15-
})
16-
})
15+
});
16+
});
1717

18-
test('should infer types for callbacks', () => {
18+
test("should infer types for callbacks", () => {
1919
return queryOptions({
20-
queryKey: ['key'],
20+
queryKey: ["key"],
2121
queryFn: () => Promise.resolve(5),
2222
staleTime: 1000,
2323
select: (data) => {
24-
expectTypeOf(data).toEqualTypeOf<number>()
24+
expectTypeOf(data).toEqualTypeOf<number>();
2525
},
26-
})
27-
})
28-
})
29-
30-
test('should work when passed to injectQuery', () => {
26+
});
27+
});
28+
29+
test("should allow undefined response in initialData", () => {
30+
return (id: string | null) =>
31+
queryOptions({
32+
queryKey: ["todo", id],
33+
queryFn: () =>
34+
Promise.resolve({
35+
id: "1",
36+
title: "Do Laundry",
37+
}),
38+
initialData: () =>
39+
!id
40+
? undefined
41+
: {
42+
id,
43+
title: "Initial Data",
44+
},
45+
});
46+
});
47+
});
48+
49+
test("should work when passed to injectQuery", () => {
3150
const options = queryOptions({
32-
queryKey: ['key'],
51+
queryKey: ["key"],
3352
queryFn: () => Promise.resolve(5),
34-
})
53+
});
3554

36-
const { data } = injectQuery(() => options)
37-
expectTypeOf(data).toEqualTypeOf<Signal<number | undefined>>()
38-
})
55+
const { data } = injectQuery(() => options);
56+
expectTypeOf(data).toEqualTypeOf<Signal<number | undefined>>();
57+
});
3958

40-
test('should work when passed to fetchQuery', () => {
59+
test("should work when passed to fetchQuery", () => {
4160
const options = queryOptions({
42-
queryKey: ['key'],
61+
queryKey: ["key"],
4362
queryFn: () => Promise.resolve(5),
44-
})
63+
});
4564

46-
const data = new QueryClient().fetchQuery(options)
47-
assertType<Promise<number>>(data)
48-
})
65+
const data = new QueryClient().fetchQuery(options);
66+
assertType<Promise<number>>(data);
67+
});
4968

50-
test('should tag the queryKey with the result type of the QueryFn', () => {
69+
test("should tag the queryKey with the result type of the QueryFn", () => {
5170
const { queryKey } = queryOptions({
52-
queryKey: ['key'],
71+
queryKey: ["key"],
5372
queryFn: () => Promise.resolve(5),
54-
})
55-
assertType<number>(queryKey[dataTagSymbol])
56-
})
73+
});
74+
assertType<number>(queryKey[dataTagSymbol]);
75+
});
5776

58-
test('should tag the queryKey even if no promise is returned', () => {
77+
test("should tag the queryKey even if no promise is returned", () => {
5978
const { queryKey } = queryOptions({
60-
queryKey: ['key'],
79+
queryKey: ["key"],
6180
queryFn: () => 5,
62-
})
63-
assertType<number>(queryKey[dataTagSymbol])
64-
})
81+
});
82+
assertType<number>(queryKey[dataTagSymbol]);
83+
});
6584

66-
test('should tag the queryKey with unknown if there is no queryFn', () => {
85+
test("should tag the queryKey with unknown if there is no queryFn", () => {
6786
const { queryKey } = queryOptions({
68-
queryKey: ['key'],
69-
})
87+
queryKey: ["key"],
88+
});
7089

71-
assertType<unknown>(queryKey[dataTagSymbol])
72-
})
90+
assertType<unknown>(queryKey[dataTagSymbol]);
91+
});
7392

74-
test('should tag the queryKey with the result type of the QueryFn if select is used', () => {
93+
test("should tag the queryKey with the result type of the QueryFn if select is used", () => {
7594
const { queryKey } = queryOptions({
76-
queryKey: ['key'],
95+
queryKey: ["key"],
7796
queryFn: () => Promise.resolve(5),
7897
select: (data) => data.toString(),
79-
})
98+
});
8099

81-
assertType<number>(queryKey[dataTagSymbol])
82-
})
100+
assertType<number>(queryKey[dataTagSymbol]);
101+
});
83102

84-
test('should return the proper type when passed to getQueryData', () => {
103+
test("should return the proper type when passed to getQueryData", () => {
85104
const { queryKey } = queryOptions({
86-
queryKey: ['key'],
105+
queryKey: ["key"],
87106
queryFn: () => Promise.resolve(5),
88-
})
107+
});
89108

90-
const queryClient = new QueryClient()
91-
const data = queryClient.getQueryData(queryKey)
109+
const queryClient = new QueryClient();
110+
const data = queryClient.getQueryData(queryKey);
92111

93-
expectTypeOf(data).toEqualTypeOf<number | undefined>()
94-
})
112+
expectTypeOf(data).toEqualTypeOf<number | undefined>();
113+
});
95114

96-
test('should properly type updaterFn when passed to setQueryData', () => {
115+
test("should properly type updaterFn when passed to setQueryData", () => {
97116
const { queryKey } = queryOptions({
98-
queryKey: ['key'],
117+
queryKey: ["key"],
99118
queryFn: () => Promise.resolve(5),
100-
})
119+
});
101120

102-
const queryClient = new QueryClient()
121+
const queryClient = new QueryClient();
103122
const data = queryClient.setQueryData(queryKey, (prev) => {
104-
expectTypeOf(prev).toEqualTypeOf<number | undefined>()
105-
return prev
106-
})
123+
expectTypeOf(prev).toEqualTypeOf<number | undefined>();
124+
return prev;
125+
});
107126

108-
expectTypeOf(data).toEqualTypeOf<number | undefined>()
109-
})
127+
expectTypeOf(data).toEqualTypeOf<number | undefined>();
128+
});
110129

111-
test('should properly type value when passed to setQueryData', () => {
130+
test("should properly type value when passed to setQueryData", () => {
112131
const { queryKey } = queryOptions({
113-
queryKey: ['key'],
132+
queryKey: ["key"],
114133
queryFn: () => Promise.resolve(5),
115-
})
134+
});
116135

117-
const queryClient = new QueryClient()
136+
const queryClient = new QueryClient();
118137

119138
// @ts-expect-error value should be a number
120-
queryClient.setQueryData(queryKey, '5')
139+
queryClient.setQueryData(queryKey, "5");
121140
// @ts-expect-error value should be a number
122-
queryClient.setQueryData(queryKey, () => '5')
141+
queryClient.setQueryData(queryKey, () => "5");
123142

124-
const data = queryClient.setQueryData(queryKey, 5)
143+
const data = queryClient.setQueryData(queryKey, 5);
125144

126-
expectTypeOf(data).toEqualTypeOf<number | undefined>()
127-
})
145+
expectTypeOf(data).toEqualTypeOf<number | undefined>();
146+
});
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,21 @@
1-
import type { DataTag, DefaultError, QueryKey } from '@tanstack/query-core'
2-
import type { CreateQueryOptions } from './types'
1+
import {
2+
DataTag,
3+
DefaultError,
4+
QueryKey,
5+
InitialDataFunction,
6+
} from "@tanstack/query-core";
7+
import type { CreateQueryOptions } from "./types";
38

49
export type UndefinedInitialDataOptions<
510
TQueryFnData = unknown,
611
TError = DefaultError,
712
TData = TQueryFnData,
813
TQueryKey extends QueryKey = QueryKey,
914
> = CreateQueryOptions<TQueryFnData, TError, TData, TQueryKey> & {
10-
initialData?: undefined
11-
}
15+
initialData?: undefined;
16+
};
1217

13-
type NonUndefinedGuard<T> = T extends undefined ? never : T
18+
type NonUndefinedGuard<T> = T extends undefined ? never : T;
1419

1520
export type DefinedInitialDataOptions<
1621
TQueryFnData = unknown,
@@ -20,8 +25,8 @@ export type DefinedInitialDataOptions<
2025
> = CreateQueryOptions<TQueryFnData, TError, TData, TQueryKey> & {
2126
initialData:
2227
| NonUndefinedGuard<TQueryFnData>
23-
| (() => NonUndefinedGuard<TQueryFnData>)
24-
}
28+
| InitialDataFunction<NonUndefinedGuard<TQueryFnData>>;
29+
};
2530

2631
export function queryOptions<
2732
TQueryFnData = unknown,
@@ -31,8 +36,8 @@ export function queryOptions<
3136
>(
3237
options: UndefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey>,
3338
): UndefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey> & {
34-
queryKey: DataTag<TQueryKey, TQueryFnData>
35-
}
39+
queryKey: DataTag<TQueryKey, TQueryFnData>;
40+
};
3641

3742
export function queryOptions<
3843
TQueryFnData = unknown,
@@ -42,9 +47,9 @@ export function queryOptions<
4247
>(
4348
options: DefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey>,
4449
): DefinedInitialDataOptions<TQueryFnData, TError, TData, TQueryKey> & {
45-
queryKey: DataTag<TQueryKey, TQueryFnData>
46-
}
50+
queryKey: DataTag<TQueryKey, TQueryFnData>;
51+
};
4752

4853
export function queryOptions(options: unknown) {
49-
return options
54+
return options;
5055
}

0 commit comments

Comments
 (0)