Skip to content

Commit f3b7440

Browse files
committed
add infinite queries
1 parent bea4b02 commit f3b7440

File tree

5 files changed

+89
-12
lines changed

5 files changed

+89
-12
lines changed

examples/vite/src/App.tsx

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,17 @@ const baseMutation = baseQuery.asMutationBuilder();
2121

2222
const resetMutation = baseMutation.withPath('/reset').withUpdates('*');
2323

24-
const postsQuery = baseQuery.withTags('refreshable', 'posts').withPath('/posts').withData<PostData[]>().withSearch<{ page?: number }>();
24+
const postsQuery = baseQuery
25+
.withTags('refreshable', 'posts')
26+
.withPath('/posts')
27+
.withData<PostData[]>()
28+
.withSearch<{ page?: number }>()
29+
.withConfig({
30+
options: {
31+
initialPageParam: { search: { page: 0 } },
32+
getNextPageParam: (prev, __, lastVars) => (!prev?.length ? null : { search: { page: (lastVars?.search?.page || 0) + 1 } }),
33+
},
34+
});
2535

2636
const postQuery = baseQuery
2737
.withTags('refreshable')
@@ -77,7 +87,7 @@ const deletePostMutation = baseMutation.withMethod('delete').withPath('/posts/:i
7787
function App() {
7888
const [postId, setPostId] = useState<number | null>(null);
7989

80-
const posts = postsQuery.useQuery({}, { enabled: !postId });
90+
const posts = postsQuery.useInfiniteQuery({}, { enabled: !postId });
8191
const reset = resetMutation.useMutation();
8292

8393
const deleteErrors = deletePostMutation.useMutationState(undefined, { status: 'error' }, (x) => x.state.variables?.params.id);
@@ -100,7 +110,7 @@ function App() {
100110
? 'Loading...'
101111
: posts.isError
102112
? posts.error.message
103-
: posts.data?.map((post) => (
113+
: posts.data?.pages?.flat().map((post) => (
104114
<div key={post.id}>
105115
<a>
106116
<h2
@@ -125,6 +135,12 @@ function App() {
125135
<p>{post.body}</p>
126136
</div>
127137
))}
138+
139+
{posts.hasNextPage && (
140+
<button onClick={() => posts.fetchNextPage()} disabled={posts.isFetchingNextPage}>
141+
Load next page
142+
</button>
143+
)}
128144
</>
129145
);
130146
}

src/builder/MutationBuilderFrozen.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,7 @@ export type MutationBuilderConfig<TVars, TData, TError, TKey extends unknown[]>
2121

2222
export class MutationBuilderFrozen<TVars, TData, TError, TKey extends unknown[], TTags extends Record<string, unknown>> {
2323
protected declare _config: MutationBuilderConfig<TVars, TData, TError, TKey>;
24-
protected declare _options: typeof this._config.options;
25-
protected declare _vars: TVars;
24+
protected declare _options: UseMutationOptions<TData, TError, TVars>;
2625

2726
constructor(
2827
public config: typeof this._config,

src/builder/QueryBuilderClient.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,27 @@ export class QueryBuilderClient<TVars, TData, TError, TKey extends unknown[], TF
88
readonly ensureData = (vars: TVars, opts?: typeof this._options) =>
99
this.builder.config.queryClient?.ensureQueryData(this.builder.getQueryOptions(vars, opts));
1010

11+
readonly ensureInfiniteData = (vars: TVars, opts?: typeof this._options) =>
12+
this.builder.config.queryClient?.ensureInfiniteQueryData(this.builder.getInfiniteQueryOptions(vars, opts));
13+
1114
readonly refetch = (vars: TVars, filters?: TFilters, opts?: RefetchOptions) =>
1215
this.builder.config.queryClient?.refetchQueries({ queryKey: this.builder.getQueryKey(vars), ...filters }, opts);
1316

1417
readonly fetch = (vars: TVars, opts?: typeof this._options) =>
1518
this.builder.config.queryClient?.fetchQuery(this.builder.getQueryOptions(vars, opts));
1619

20+
readonly fetchInfinite = (vars: TVars, opts?: typeof this._options) =>
21+
this.builder.config.queryClient?.fetchInfiniteQuery(this.builder.getInfiniteQueryOptions(vars, opts));
22+
1723
readonly isFetching = (vars: TVars, filters?: TFilters) =>
1824
this.builder.config.queryClient?.isFetching({ queryKey: this.builder.getQueryKey(vars), ...filters });
1925

2026
readonly prefetch = (vars: TVars, opts?: typeof this._options) =>
2127
this.builder.config.queryClient?.prefetchQuery(this.builder.getQueryOptions(vars, opts));
2228

29+
readonly prefetchInfinite = (vars: TVars, opts?: typeof this._options) =>
30+
this.builder.config.queryClient?.prefetchInfiniteQuery(this.builder.getInfiniteQueryOptions(vars, opts));
31+
2332
readonly reset = (vars: TVars, filters?: TFilters, opts?: ResetOptions) =>
2433
this.builder.config.queryClient?.resetQueries({ queryKey: this.builder.getQueryKey(vars), ...filters }, opts);
2534

src/builder/QueryBuilderFrozen.ts

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,34 @@
11
import {
22
DataTag,
3+
InfiniteData,
4+
InfiniteQueryPageParamsOptions,
35
QueryFilters,
46
QueryFunction,
7+
UseInfiniteQueryOptions,
8+
UseInfiniteQueryResult,
59
UseQueryOptions,
10+
UseSuspenseInfiniteQueryResult,
11+
useInfiniteQuery,
612
useIsFetching,
13+
usePrefetchInfiniteQuery,
714
usePrefetchQuery,
815
useQueries,
916
useQuery,
17+
useSuspenseInfiniteQuery,
1018
useSuspenseQueries,
1119
useSuspenseQuery,
1220
} from '@tanstack/react-query';
13-
import { FunctionType } from '../types/utils';
21+
import { FunctionType, TODO } from '../types/utils';
1422
import { QueryBuilderClient } from './QueryBuilderClient';
1523
import { BuilderConfig, BuilderQueriesResult } from './types';
1624
import { mergeQueryOptions, mergeVars } from './utils';
1725

26+
export type QueryBuilderOptions<TVars, TData, TError, TKey extends unknown[]> = Partial<
27+
UseQueryOptions<TData, TError, TData, TKey> & { queryFn: FunctionType } & InfiniteQueryPageParamsOptions<TData, Partial<TVars>>
28+
>;
29+
1830
export type QueryBuilderConfig<TVars, TData, TError, TKey extends unknown[]> = BuilderConfig<TVars, TData, TError, TKey> & {
19-
options?: Partial<UseQueryOptions<TData, TError, TData, TKey> & { queryFn: FunctionType }>;
31+
options?: QueryBuilderOptions<TVars, TData, TError, TKey>;
2032
};
2133

2234
export class QueryBuilderFrozen<
@@ -27,8 +39,7 @@ export class QueryBuilderFrozen<
2739
TTags extends Record<string, unknown> = Record<string, unknown>,
2840
> {
2941
protected declare _config: QueryBuilderConfig<TVars, TData, TError, TKey>;
30-
protected declare _options: typeof this._config.options;
31-
protected declare _vars: TVars;
42+
protected declare _options: QueryBuilderOptions<TVars, TData, TError, TKey>;
3243

3344
constructor(public config: typeof this._config) {}
3445

@@ -50,7 +61,7 @@ export class QueryBuilderFrozen<
5061
return this.config.preprocessorFn(vars);
5162
};
5263

53-
getQueryFn: () => QueryFunction<TData, TKey> = () => {
64+
getQueryFn: () => QueryFunction<TData, TKey, Partial<TVars>> = () => {
5465
return ({ client, meta, queryKey, signal, pageParam }) => {
5566
return this.config.queryFn({ client, meta, queryKey, signal, pageParam, originalQueryKey: queryKey });
5667
};
@@ -75,6 +86,16 @@ export class QueryBuilderFrozen<
7586
]);
7687
};
7788

89+
getInfiniteQueryOptions: (
90+
vars: TVars,
91+
opts?: typeof this._options,
92+
) => UseInfiniteQueryOptions<TData, TError, InfiniteData<TData, Partial<TVars>>, TData, TKey> & { queryFn: FunctionType } = (
93+
vars,
94+
opts,
95+
) => {
96+
return this.getQueryOptions(vars, opts) as TODO;
97+
};
98+
7899
useQuery: (vars: TVars, opts?: typeof this._options) => ReturnType<typeof useQuery<TData, TError, TData, TKey>> = (vars, opts) => {
79100
return useQuery(this.getQueryOptions(vars, opts), this.config.queryClient);
80101
};
@@ -135,6 +156,24 @@ export class QueryBuilderFrozen<
135156
return this.useQueriesInternal(useSuspenseQueries)(queries, sharedVars, sharedOpts);
136157
};
137158

159+
useInfiniteQuery: (vars: TVars, opts?: typeof this._options) => UseInfiniteQueryResult<InfiniteData<TData, Partial<TVars>>, TError> = (
160+
vars,
161+
opts,
162+
) => {
163+
return useInfiniteQuery(this.getInfiniteQueryOptions(vars, opts), this.config.queryClient);
164+
};
165+
166+
usePrefetchInfiniteQuery: (vars: TVars, opts?: typeof this._options) => void = (vars, opts) => {
167+
return usePrefetchInfiniteQuery(this.getInfiniteQueryOptions(vars, opts), this.config.queryClient);
168+
};
169+
170+
useSuspenseInfiniteQuery: (
171+
vars: TVars,
172+
opts?: typeof this._options,
173+
) => UseSuspenseInfiniteQueryResult<InfiniteData<TData, Partial<TVars>>, TError> = (vars, opts) => {
174+
return useSuspenseInfiniteQuery(this.getInfiniteQueryOptions(vars, opts), this.config.queryClient);
175+
};
176+
138177
private _client?: QueryBuilderClient<TVars, TData, TError, TKey>;
139178
get client() {
140179
return (this._client ??= new QueryBuilderClient(this));

src/tags/updateTags.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { type QueryClient, type QueryState, useQueryClient } from '@tanstack/react-query';
1+
import { type InfiniteData, InfiniteQueryObserver, type QueryClient, type QueryState, useQueryClient } from '@tanstack/react-query';
22
import { useStableCallback } from '../hooks/useStableCallback';
33
import type { WithOptional } from '../types/utils';
44
import { queryMatchesTag } from './operateOnTags';
@@ -46,7 +46,21 @@ export function updateTags({
4646

4747
for (const q of list) {
4848
undos.push({ hash: q.queryHash, data: q.state.data });
49-
setDataToExistingQuery(queryClient, q.queryHash, updaterFn(ctx, q.state.data), willInvalidate ? { isInvalidated: true } : undefined, {
49+
50+
let newData: unknown;
51+
if (q.observers[0] && q.observers[0] instanceof InfiniteQueryObserver) {
52+
const data = q.state.data as InfiniteData<unknown>;
53+
if (data.pages && Array.isArray(data.pages)) {
54+
newData = {
55+
...data,
56+
pages: data.pages.map((page) => updaterFn(ctx, page)),
57+
} as InfiniteData<unknown>;
58+
}
59+
} else {
60+
newData = updaterFn(ctx, q.state.data);
61+
}
62+
63+
setDataToExistingQuery(queryClient, q.queryHash, newData, willInvalidate ? { isInvalidated: true } : undefined, {
5064
updated: optimistic ? 'optimistic' : 'pessimistic',
5165
});
5266
}

0 commit comments

Comments
 (0)