Skip to content

Commit e7ebcdd

Browse files
authored
feat: RefetchOnChange should accept a function #84 (#85)
1 parent 15616f1 commit e7ebcdd

File tree

6 files changed

+106
-22
lines changed

6 files changed

+106
-22
lines changed

packages/mst-query/package-lock.json

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

packages/mst-query/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "mst-query",
3-
"version": "4.0.5",
3+
"version": "4.1.1",
44
"description": "Query library for mobx-state-tree",
55
"source": "src/index.ts",
66
"type": "module",

packages/mst-query/src/MstQueryHandler.ts

+22-4
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ type QueryHookOptions = {
2929
enabled?: boolean;
3030
isMounted?: any;
3131
isRequestEqual?: boolean;
32+
isReenabled?: boolean;
3233
refetchOnMount?: 'always' | 'never' | 'if-stale';
3334
refetchOnChanged?: 'all' | 'request' | 'pagination' | 'none';
3435
};
@@ -46,7 +47,7 @@ export class DisposedError extends Error {}
4647

4748
export class QueryObserver {
4849
query: any;
49-
options: any;
50+
options: any = {};
5051
isQuery: boolean;
5152
isMounted = false;
5253
isFetchedAfterMount = false;
@@ -77,13 +78,15 @@ export class QueryObserver {
7778
}
7879

7980
setOptions(options: any) {
80-
this.options = options;
81-
8281
this.subscribe();
8382

8483
if (this.isQuery) {
8584
options.isMounted = this.isMounted;
8685

86+
if (!this.options.enabled && options.enabled) {
87+
options.isReenabled = true;
88+
}
89+
8790
const refetchRequestOnChanged =
8891
options.refetchOnChanged === 'all' || options.refetchOnChanged === 'request';
8992
options.isRequestEqual = true;
@@ -99,6 +102,19 @@ export class QueryObserver {
99102
}
100103
}
101104

105+
if (options.enabled && typeof options.refetchOnChanged === 'function') {
106+
if (this.query.variables.request && this.query.variables.pagination) {
107+
options.isRequestEqual = !options.refetchOnChanged({
108+
prevRequest: this.query.variables.request,
109+
prevPagination: this.query.variables.pagination,
110+
});
111+
} else if (this.query.variables.request) {
112+
options.isRequestEqual = !options.refetchOnChanged({
113+
prevRequest: this.query.variables.request,
114+
});
115+
}
116+
}
117+
102118
if (options.initialData && !options.isMounted) {
103119
const isStale = isDataStale(options.initialDataUpdatedAt, options.staleTime);
104120
if (!isStale) {
@@ -118,6 +134,8 @@ export class QueryObserver {
118134
if (!this.isMounted) {
119135
this.isMounted = true;
120136
}
137+
138+
this.options = options;
121139
}
122140
}
123141

@@ -276,7 +294,7 @@ export class MstQueryHandler {
276294
return this.model.query(options);
277295
}
278296

279-
if (notInitialized && options.refetchOnChanged === 'none') {
297+
if (notInitialized && options.isReenabled) {
280298
return this.model.query(options);
281299
}
282300

packages/mst-query/src/hooks.ts

+36-14
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import { Instance, SnapshotIn } from 'mobx-state-tree';
22
import { useContext, useEffect, useRef, useState } from 'react';
3-
import { VolatileQuery, MutationReturnType, QueryReturnType, InfiniteQueryReturnType } from './create';
3+
import {
4+
VolatileQuery,
5+
MutationReturnType,
6+
QueryReturnType,
7+
InfiniteQueryReturnType,
8+
} from './create';
49
import { Context } from './QueryClientProvider';
510
import { QueryClient } from './QueryClient';
611
import { EmptyPagination, EmptyRequest, QueryObserver } from './MstQueryHandler';
@@ -16,7 +21,12 @@ function mergeWithDefaultOptions(key: string, options: any, queryClient: QueryCl
1621
type QueryOptions<T extends Instance<QueryReturnType>> = {
1722
request?: SnapshotIn<T['variables']['request']>;
1823
refetchOnMount?: 'always' | 'never' | 'if-stale';
19-
refetchOnChanged?: 'all' | 'request' | 'pagination' | 'none';
24+
refetchOnChanged?:
25+
| 'all'
26+
| 'request'
27+
| 'pagination'
28+
| 'none'
29+
| ((options: { prevRequest: Exclude<T['variables']['request'], undefined> }) => boolean);
2030
staleTime?: number;
2131
enabled?: boolean;
2232
initialData?: any;
@@ -26,7 +36,7 @@ type QueryOptions<T extends Instance<QueryReturnType>> = {
2636

2737
export function useQuery<T extends Instance<QueryReturnType>>(
2838
query: T,
29-
options: QueryOptions<T> = {}
39+
options: QueryOptions<T> = {},
3040
) {
3141
const [observer, setObserver] = useState(() => new QueryObserver(query, true));
3242

@@ -36,7 +46,9 @@ export function useQuery<T extends Instance<QueryReturnType>>(
3646
(options as any).request = options.request ?? EmptyRequest;
3747

3848
if ((query as any).isInfinite) {
39-
throw new Error('useQuery should be used with a query that does not have pagination. Use useInfiniteQuery instead.');
49+
throw new Error(
50+
'useQuery should be used with a query that does not have pagination. Use useInfiniteQuery instead.',
51+
);
4052
}
4153

4254
useEffect(() => {
@@ -54,7 +66,7 @@ export function useQuery<T extends Instance<QueryReturnType>>(
5466
}, [options]);
5567

5668
return {
57-
data: query.data as typeof query['data'],
69+
data: query.data as (typeof query)['data'],
5870
dataUpdatedAt: query.__MstQueryHandler.cachedAt?.getTime(),
5971
error: query.error,
6072
isFetched: query.isFetched,
@@ -71,7 +83,15 @@ type InfiniteQueryOptions<T extends Instance<InfiniteQueryReturnType>> = {
7183
request?: SnapshotIn<T['variables']['request']>;
7284
pagination?: SnapshotIn<T['variables']['pagination']>;
7385
refetchOnMount?: 'always' | 'never' | 'if-stale';
74-
refetchOnChanged?: 'all' | 'request' | 'pagination' | 'none';
86+
refetchOnChanged?:
87+
| 'all'
88+
| 'request'
89+
| 'pagination'
90+
| 'none'
91+
| ((options: {
92+
prevRequest: Exclude<T['variables']['request'], undefined>;
93+
prevPagination: Exclude<T['variables']['pagination'], undefined>;
94+
}) => boolean);
7595
staleTime?: number;
7696
enabled?: boolean;
7797
initialData?: any;
@@ -81,7 +101,7 @@ type InfiniteQueryOptions<T extends Instance<InfiniteQueryReturnType>> = {
81101

82102
export function useInfiniteQuery<T extends Instance<InfiniteQueryReturnType>>(
83103
query: T,
84-
options: InfiniteQueryOptions<T> = {}
104+
options: InfiniteQueryOptions<T> = {},
85105
) {
86106
const [observer, setObserver] = useState(() => new QueryObserver(query, true));
87107

@@ -90,9 +110,11 @@ export function useInfiniteQuery<T extends Instance<InfiniteQueryReturnType>>(
90110

91111
(options as any).request = options.request ?? EmptyRequest;
92112
(options as any).pagination = options.pagination ?? EmptyPagination;
93-
113+
94114
if (!(query as any).isInfinite) {
95-
throw new Error('useInfiniteQuery should be used with a query that has pagination. Use useQuery instead.');
115+
throw new Error(
116+
'useInfiniteQuery should be used with a query that has pagination. Use useQuery instead.',
117+
);
96118
}
97119

98120
useEffect(() => {
@@ -110,7 +132,7 @@ export function useInfiniteQuery<T extends Instance<InfiniteQueryReturnType>>(
110132
}, [options]);
111133

112134
return {
113-
data: query.data as typeof query['data'],
135+
data: query.data as (typeof query)['data'],
114136
dataUpdatedAt: query.__MstQueryHandler.cachedAt?.getTime(),
115137
error: query.error,
116138
isFetched: query.isFetched,
@@ -131,7 +153,7 @@ type MutationOptions<T extends Instance<MutationReturnType>> = {
131153

132154
export function useMutation<T extends Instance<MutationReturnType>>(
133155
mutation: T,
134-
options: MutationOptions<T> = {}
156+
options: MutationOptions<T> = {},
135157
) {
136158
const [observer, setObserver] = useState(() => new QueryObserver(mutation, false));
137159

@@ -149,7 +171,7 @@ export function useMutation<T extends Instance<MutationReturnType>>(
149171
}, [options]);
150172

151173
const result = {
152-
data: mutation.data as typeof mutation['data'],
174+
data: mutation.data as (typeof mutation)['data'],
153175
error: mutation.error,
154176
isLoading: mutation.isLoading,
155177
mutation,
@@ -162,7 +184,7 @@ export function useMutation<T extends Instance<MutationReturnType>>(
162184
}) => {
163185
const result = mutation.mutate({ ...params, ...options } as any);
164186
return result as Promise<{ data: T['data']; error: any; result: TResult }>;
165-
}
187+
},
166188
);
167189

168190
return [mutate, result] as [typeof mutate, typeof result];
@@ -181,7 +203,7 @@ type UseVolatileQueryOptions<T extends Instance<QueryReturnType>> = QueryOptions
181203
};
182204

183205
export function useVolatileQuery(
184-
options: UseVolatileQueryOptions<Instance<typeof VolatileQuery>> = {}
206+
options: UseVolatileQueryOptions<Instance<typeof VolatileQuery>> = {},
185207
) {
186208
const queryClient = useContext(Context)! as QueryClient<any>;
187209
const query = useRefQuery(VolatileQuery, queryClient);

packages/mst-query/tests/models/ItemQuery.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { api } from '../api/api';
44
import { ItemModel } from './ItemModel';
55

66
export const ItemQuery = createQuery('ItemQuery', {
7-
request: types.model({ id: types.string }),
7+
request: types.model({ id: types.string, id2: types.maybe(types.string) }),
88
data: types.reference(ItemModel),
99
async endpoint(args) {
1010
return args.meta.getItem ? args.meta.getItem(args) : api.getItem(args);

packages/mst-query/tests/mstQuery.test.tsx

+44
Original file line numberDiff line numberDiff line change
@@ -978,6 +978,50 @@ test('useQuery should run when initialData is given and invalidate is called', a
978978
configureMobx({ enforceActions: 'observed' });
979979
});
980980

981+
test('refetchOnRequestChanged function', async () => {
982+
const { render, q } = setup();
983+
984+
configureMobx({ enforceActions: 'never' });
985+
986+
let id = observable.box('test');
987+
let id2 = observable.box('test2');
988+
989+
const getItem = vi.fn(() => Promise.resolve(itemData));
990+
const testApi = {
991+
...api,
992+
getItem: () => getItem(),
993+
};
994+
995+
const Comp = observer(() => {
996+
useQuery(q.itemQuery, {
997+
request: { id: id.get(), id2: id2.get() },
998+
refetchOnChanged({ prevRequest }) {
999+
return prevRequest.id !== id.get();
1000+
},
1001+
staleTime: 5000,
1002+
meta: { getItem: testApi.getItem },
1003+
});
1004+
return <div></div>;
1005+
});
1006+
1007+
render(<Comp />);
1008+
await wait(0);
1009+
1010+
expect(getItem).toHaveBeenCalledTimes(1);
1011+
1012+
id.set('different-test');
1013+
await wait(0);
1014+
1015+
expect(getItem).toHaveBeenCalledTimes(2);
1016+
1017+
id2.set('different-test2');
1018+
await wait(0);
1019+
1020+
expect(getItem).toHaveBeenCalledTimes(2);
1021+
1022+
configureMobx({ enforceActions: 'observed' });
1023+
});
1024+
9811025
test('refetchOnMount & refetchOnRequestChanged', async () => {
9821026
const { render, q } = setup();
9831027

0 commit comments

Comments
 (0)