floppy-disk 2.0.0-beta.1 → 2.0.0-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm/index.d.ts +1 -5
- package/esm/index.js +1 -5
- package/esm/preact/create-mutation.d.ts +28 -0
- package/esm/preact/create-mutation.js +50 -0
- package/esm/preact/create-query.d.ts +197 -0
- package/esm/preact/create-query.js +333 -0
- package/esm/preact/create-store.d.ts +28 -0
- package/esm/preact/create-store.js +35 -0
- package/esm/preact/create-stores.d.ts +45 -0
- package/esm/preact/create-stores.js +96 -0
- package/esm/preact/index.d.ts +5 -0
- package/esm/preact/index.js +5 -0
- package/esm/preact/with-context.d.ts +6 -0
- package/esm/preact/with-context.js +16 -0
- package/esm/react/create-query.d.ts +11 -0
- package/esm/react/create-query.js +4 -1
- package/esm/react/index.d.ts +5 -0
- package/esm/react/index.js +5 -0
- package/lib/index.d.ts +1 -5
- package/lib/index.js +1 -5
- package/lib/preact/create-mutation.d.ts +28 -0
- package/lib/preact/create-mutation.js +54 -0
- package/lib/preact/create-query.d.ts +197 -0
- package/lib/preact/create-query.js +337 -0
- package/lib/preact/create-store.d.ts +28 -0
- package/lib/preact/create-store.js +39 -0
- package/lib/preact/create-stores.d.ts +45 -0
- package/lib/preact/create-stores.js +100 -0
- package/lib/preact/index.d.ts +5 -0
- package/lib/preact/index.js +8 -0
- package/lib/preact/with-context.d.ts +6 -0
- package/lib/preact/with-context.js +20 -0
- package/lib/react/create-query.d.ts +11 -0
- package/lib/react/create-query.js +4 -1
- package/lib/react/index.d.ts +5 -0
- package/lib/react/index.js +8 -0
- package/package.json +25 -7
package/esm/index.d.ts
CHANGED
package/esm/index.js
CHANGED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { InitStoreOptions } from '../vanilla';
|
|
2
|
+
export type MutationState<TVar, TResponse = any, TError = unknown> = {
|
|
3
|
+
/**
|
|
4
|
+
* Network fetching status.
|
|
5
|
+
*/
|
|
6
|
+
isWaiting: boolean;
|
|
7
|
+
isSuccess: boolean;
|
|
8
|
+
isError: boolean;
|
|
9
|
+
response: TResponse | null;
|
|
10
|
+
responseUpdatedAt: number | null;
|
|
11
|
+
error: TError | null;
|
|
12
|
+
errorUpdatedAt: number | null;
|
|
13
|
+
/**
|
|
14
|
+
* Mutate function is a promise that will always resolve.
|
|
15
|
+
*/
|
|
16
|
+
mutate: (variables?: TVar) => Promise<{
|
|
17
|
+
response?: TResponse;
|
|
18
|
+
error?: TError;
|
|
19
|
+
variables?: TVar;
|
|
20
|
+
}>;
|
|
21
|
+
};
|
|
22
|
+
export type CreateMutationOptions<TVar, TResponse = any, TError = unknown> = InitStoreOptions<MutationState<TVar, TResponse, TError>> & {
|
|
23
|
+
onMutate?: (variables: TVar | undefined, stateBeforeMutate: MutationState<TVar, TResponse, TError>) => void;
|
|
24
|
+
onSuccess?: (response: TResponse, variables: TVar | undefined, stateBeforeMutate: MutationState<TVar, TResponse, TError>) => void;
|
|
25
|
+
onError?: (error: TError, variables: TVar | undefined, stateBeforeMutate: MutationState<TVar, TResponse, TError>) => void;
|
|
26
|
+
onSettled?: (variables: TVar | undefined, stateBeforeMutate: MutationState<TVar, TResponse, TError>) => void;
|
|
27
|
+
};
|
|
28
|
+
export declare const createMutation: <TVar, TResponse = any, TError = unknown>(mutationFn: (variables: TVar | undefined, state: MutationState<TVar, TResponse, TError>) => Promise<TResponse>, options?: CreateMutationOptions<TVar, TResponse, TError>) => import("./create-store").UseStore<MutationState<TVar, TResponse, TError>>;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { noop } from '../utils';
|
|
2
|
+
import { createStore } from './create-store';
|
|
3
|
+
export const createMutation = (mutationFn, options = {}) => {
|
|
4
|
+
const { onMutate = noop, onSuccess = noop, onError = noop, onSettled = noop, ...createStoreOptions } = options;
|
|
5
|
+
const useMutation = createStore(({ set, get }) => ({
|
|
6
|
+
isWaiting: false,
|
|
7
|
+
isSuccess: false,
|
|
8
|
+
isError: false,
|
|
9
|
+
response: null,
|
|
10
|
+
responseUpdatedAt: null,
|
|
11
|
+
error: null,
|
|
12
|
+
errorUpdatedAt: null,
|
|
13
|
+
mutate: (variables) => {
|
|
14
|
+
set({ isWaiting: true });
|
|
15
|
+
const stateBeforeMutate = get();
|
|
16
|
+
onMutate(variables, stateBeforeMutate);
|
|
17
|
+
return new Promise((resolve) => {
|
|
18
|
+
mutationFn(variables, stateBeforeMutate)
|
|
19
|
+
.then((response) => {
|
|
20
|
+
set({
|
|
21
|
+
isWaiting: false,
|
|
22
|
+
isSuccess: true,
|
|
23
|
+
isError: false,
|
|
24
|
+
response,
|
|
25
|
+
responseUpdatedAt: Date.now(),
|
|
26
|
+
error: null,
|
|
27
|
+
errorUpdatedAt: null,
|
|
28
|
+
});
|
|
29
|
+
onSuccess(response, variables, stateBeforeMutate);
|
|
30
|
+
resolve({ response, variables });
|
|
31
|
+
})
|
|
32
|
+
.catch((error) => {
|
|
33
|
+
set({
|
|
34
|
+
isWaiting: false,
|
|
35
|
+
isSuccess: false,
|
|
36
|
+
isError: true,
|
|
37
|
+
error,
|
|
38
|
+
errorUpdatedAt: Date.now(),
|
|
39
|
+
});
|
|
40
|
+
onError(error, variables, stateBeforeMutate);
|
|
41
|
+
resolve({ error, variables });
|
|
42
|
+
})
|
|
43
|
+
.finally(() => {
|
|
44
|
+
onSettled(variables, stateBeforeMutate);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
},
|
|
48
|
+
}), createStoreOptions);
|
|
49
|
+
return useMutation;
|
|
50
|
+
};
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { CreateStoresOptions, StoreKey, UseStores } from './create-stores';
|
|
2
|
+
export type QueryStatus = 'loading' | 'success' | 'error';
|
|
3
|
+
export type QueryState<TKey extends StoreKey = StoreKey, TResponse = any, TData = TResponse, TError = unknown> = {
|
|
4
|
+
/**
|
|
5
|
+
* Query store key, an object that will be hashed into a string as a query store identifier.
|
|
6
|
+
*/
|
|
7
|
+
key: TKey;
|
|
8
|
+
/**
|
|
9
|
+
* Will only be called if the data is stale or empty.
|
|
10
|
+
*/
|
|
11
|
+
fetch: () => void;
|
|
12
|
+
/**
|
|
13
|
+
* Will be called even if the data is still fresh (not stale).
|
|
14
|
+
*/
|
|
15
|
+
forceFetch: () => void;
|
|
16
|
+
/**
|
|
17
|
+
* Fetch next page if has next page.
|
|
18
|
+
*
|
|
19
|
+
* If the data is empty, it will just fetch the first page.
|
|
20
|
+
*
|
|
21
|
+
* You can ignore this if your query is not paginated.
|
|
22
|
+
*/
|
|
23
|
+
fetchNextPage: () => void;
|
|
24
|
+
/**
|
|
25
|
+
* Set query state (data, error, etc) to initial state.
|
|
26
|
+
*/
|
|
27
|
+
reset: () => void;
|
|
28
|
+
/**
|
|
29
|
+
* Optimistic update.
|
|
30
|
+
*
|
|
31
|
+
* @returns function to revert the changes & function to invalidate the query
|
|
32
|
+
*
|
|
33
|
+
* IMPORTANT NOTE: This won't work well on infinite query.
|
|
34
|
+
*/
|
|
35
|
+
optimisticUpdate: (response: TResponse | ((prevState: QueryState<TKey, TResponse, TData, TError>) => TResponse)) => {
|
|
36
|
+
revert: () => void;
|
|
37
|
+
invalidate: () => void;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Network fetching status.
|
|
41
|
+
*/
|
|
42
|
+
isWaiting: boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Network fetching status for fetching next page.
|
|
45
|
+
*/
|
|
46
|
+
isWaitingNextPage: boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Status of the data.
|
|
49
|
+
*
|
|
50
|
+
* `"loading"` = no data.
|
|
51
|
+
*
|
|
52
|
+
* `"success"` = has data.
|
|
53
|
+
*
|
|
54
|
+
* `"error"` = has error.
|
|
55
|
+
*
|
|
56
|
+
* It has no relation with network fetching state.
|
|
57
|
+
* If you're looking for network fetching state, use `isWaiting` instead.
|
|
58
|
+
*/
|
|
59
|
+
status: QueryStatus;
|
|
60
|
+
/**
|
|
61
|
+
* Data state, will be `true` if the query has no data.
|
|
62
|
+
*
|
|
63
|
+
* It has no relation with network fetching state.
|
|
64
|
+
* If you're looking for network fetching state, use `isWaiting` instead.
|
|
65
|
+
*/
|
|
66
|
+
isLoading: boolean;
|
|
67
|
+
/**
|
|
68
|
+
* Data state, will be `true` if the query has a data.
|
|
69
|
+
*/
|
|
70
|
+
isSuccess: boolean;
|
|
71
|
+
/**
|
|
72
|
+
* Error state, will be `true` after data fetching error.
|
|
73
|
+
*/
|
|
74
|
+
isError: boolean;
|
|
75
|
+
isRefetching: boolean;
|
|
76
|
+
isRefetchError: boolean;
|
|
77
|
+
isPreviousData: boolean;
|
|
78
|
+
isOptimisticData: boolean;
|
|
79
|
+
data: TData | null;
|
|
80
|
+
response: TResponse | null;
|
|
81
|
+
responseUpdatedAt: number | null;
|
|
82
|
+
error: TError | null;
|
|
83
|
+
errorUpdatedAt: number | null;
|
|
84
|
+
retryCount: number;
|
|
85
|
+
isGoingToRetry: boolean;
|
|
86
|
+
pageParam: any;
|
|
87
|
+
pageParams: any[];
|
|
88
|
+
hasNextPage: boolean;
|
|
89
|
+
retryNextPageCount: number;
|
|
90
|
+
isGoingToRetryNextPage: boolean;
|
|
91
|
+
};
|
|
92
|
+
export type CreateQueryOptions<TKey extends StoreKey = StoreKey, TResponse = any, TData = TResponse, TError = unknown> = CreateStoresOptions<TKey, QueryState<TKey, TResponse, TData, TError>> & {
|
|
93
|
+
select?: (response: TResponse, state: Pick<QueryState<TKey, TResponse, TData, TError>, 'data' | 'key'>) => TData;
|
|
94
|
+
/**
|
|
95
|
+
* Stale time in miliseconds.
|
|
96
|
+
*
|
|
97
|
+
* Defaults to `3000` (3 seconds).
|
|
98
|
+
*/
|
|
99
|
+
staleTime?: number;
|
|
100
|
+
/**
|
|
101
|
+
* Auto call the query when the component is mounted.
|
|
102
|
+
*
|
|
103
|
+
* Defaults to `true`.
|
|
104
|
+
*
|
|
105
|
+
* - If set to `true`, the query will be called on mount focus **if the data is stale**.
|
|
106
|
+
* - If set to `false`, the query won't be called on mount focus.
|
|
107
|
+
* - If set to `"always"`, the query will be called on mount focus.
|
|
108
|
+
*/
|
|
109
|
+
fetchOnMount?: boolean | 'always' | ((key: TKey) => boolean | 'always');
|
|
110
|
+
/**
|
|
111
|
+
* Defaults to `true`.
|
|
112
|
+
*
|
|
113
|
+
* - If set to `true`, the query will be called on window focus **if the data is stale**.
|
|
114
|
+
* - If set to `false`, the query won't be called on window focus.
|
|
115
|
+
* - If set to `"always"`, the query will be called on window focus.
|
|
116
|
+
*/
|
|
117
|
+
fetchOnWindowFocus?: boolean | 'always' | ((key: TKey) => boolean | 'always');
|
|
118
|
+
/**
|
|
119
|
+
* If set to `false` or return `false`, the query won't be called in any condition.
|
|
120
|
+
* Auto fetch on mount will be disabled.
|
|
121
|
+
* Manually trigger `fetch` method (returned from `createQuery`) won't work too.
|
|
122
|
+
*
|
|
123
|
+
* Defaults to `true`.
|
|
124
|
+
*/
|
|
125
|
+
enabled?: boolean | ((key: TKey) => boolean);
|
|
126
|
+
/**
|
|
127
|
+
* Number of maximum error retries.
|
|
128
|
+
*
|
|
129
|
+
* Defaults to `1`.
|
|
130
|
+
*/
|
|
131
|
+
retry?: number | ((error: TError, key: TKey) => number);
|
|
132
|
+
/**
|
|
133
|
+
* Error retry delay in miliseconds.
|
|
134
|
+
*
|
|
135
|
+
* Defaults to `3000` (3 seconds).
|
|
136
|
+
*/
|
|
137
|
+
retryDelay?: number | ((error: TError, key: TKey) => number);
|
|
138
|
+
/**
|
|
139
|
+
* If set to `true`, previous `data` will be kept when fetching new data because the query key changed.
|
|
140
|
+
*
|
|
141
|
+
* This will only happened if there is no `data` in the next query.
|
|
142
|
+
*/
|
|
143
|
+
keepPreviousData?: boolean;
|
|
144
|
+
/**
|
|
145
|
+
* Only set this if you have an infinite query.
|
|
146
|
+
*
|
|
147
|
+
* This function should return a variable that will be used when fetching next page (`pageParam`).
|
|
148
|
+
*/
|
|
149
|
+
getNextPageParam?: (lastPage: TResponse, index: number) => any;
|
|
150
|
+
onSuccess?: (response: TResponse, stateBeforeCallQuery: QueryState<TKey, TResponse, TData, TError>) => void;
|
|
151
|
+
onError?: (error: TError, stateBeforeCallQuery: QueryState<TKey, TResponse, TData, TError>) => void;
|
|
152
|
+
onSettled?: (stateBeforeCallQuery: QueryState<TKey, TResponse, TData, TError>) => void;
|
|
153
|
+
};
|
|
154
|
+
export type UseQuery<TKey extends StoreKey = StoreKey, TResponse = any, TData = TResponse, TError = unknown> = UseStores<TKey, QueryState<TKey, TResponse, TData, TError>> & {
|
|
155
|
+
/**
|
|
156
|
+
* Set query's initial response.
|
|
157
|
+
*
|
|
158
|
+
* This is used for server-side rendered page or static page.
|
|
159
|
+
*
|
|
160
|
+
* IMPORTANT NOTE: Put this on the root component or parent component, before any component subscribed!
|
|
161
|
+
*/
|
|
162
|
+
setInitialResponse: (options: {
|
|
163
|
+
key?: TKey;
|
|
164
|
+
response: TResponse;
|
|
165
|
+
}) => void;
|
|
166
|
+
/**
|
|
167
|
+
* Set query state (data, error, etc) to initial state.
|
|
168
|
+
*/
|
|
169
|
+
reset: () => void;
|
|
170
|
+
/**
|
|
171
|
+
* Set query state (data, error, etc) to initial state.
|
|
172
|
+
*/
|
|
173
|
+
resetSpecificKey: (key?: TKey | null) => void;
|
|
174
|
+
/**
|
|
175
|
+
* Invalidate query means marking a query as stale, and will refetch only if the query is active (has subscriber)
|
|
176
|
+
*/
|
|
177
|
+
invalidate: () => void;
|
|
178
|
+
/**
|
|
179
|
+
* Invalidate query means marking a query as stale, and will refetch only if the query is active (has subscriber)
|
|
180
|
+
*/
|
|
181
|
+
invalidateSpecificKey: (key?: TKey | null) => void;
|
|
182
|
+
/**
|
|
183
|
+
* Optimistic update.
|
|
184
|
+
*
|
|
185
|
+
* @returns function to revert the changes & function to invalidate the query
|
|
186
|
+
*
|
|
187
|
+
* IMPORTANT NOTE: This won't work well on infinite query.
|
|
188
|
+
*/
|
|
189
|
+
optimisticUpdate: (options: {
|
|
190
|
+
key?: TKey | null;
|
|
191
|
+
response: TResponse | ((prevState: QueryState<TKey, TResponse, TData, TError>) => TResponse);
|
|
192
|
+
}) => {
|
|
193
|
+
revert: () => void;
|
|
194
|
+
invalidate: () => void;
|
|
195
|
+
};
|
|
196
|
+
};
|
|
197
|
+
export declare const createQuery: <TKey extends StoreKey = StoreKey, TResponse = any, TData = TResponse, TError = unknown>(queryFn: (key: TKey, state: QueryState<TKey, TResponse, TData, TError>) => Promise<TResponse>, options?: CreateQueryOptions<TKey, TResponse, TData, TError>) => UseQuery<TKey, TResponse, TData, TError>;
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
import { useState } from 'preact/hooks';
|
|
2
|
+
import { hashStoreKey, identityFn, noop } from '../utils';
|
|
3
|
+
import { createStores } from './create-stores';
|
|
4
|
+
const getDecision = (value, param, { ifTrue, ifAlways }) => {
|
|
5
|
+
if (value === true || (typeof value === 'function' && value(param) === true)) {
|
|
6
|
+
ifTrue();
|
|
7
|
+
}
|
|
8
|
+
else if (value === 'always' || (typeof value === 'function' && value(param) === 'always')) {
|
|
9
|
+
ifAlways();
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
const DEFAULT_STALE_TIME = 3000; // 3 seconds
|
|
13
|
+
const INITIAL_QUERY_STATE = {
|
|
14
|
+
isWaiting: false,
|
|
15
|
+
isWaitingNextPage: false,
|
|
16
|
+
status: 'loading',
|
|
17
|
+
isLoading: true,
|
|
18
|
+
isSuccess: false,
|
|
19
|
+
isError: false,
|
|
20
|
+
isRefetching: false,
|
|
21
|
+
isRefetchError: false,
|
|
22
|
+
isPreviousData: false,
|
|
23
|
+
isOptimisticData: false,
|
|
24
|
+
data: null,
|
|
25
|
+
response: null,
|
|
26
|
+
responseUpdatedAt: null,
|
|
27
|
+
error: null,
|
|
28
|
+
errorUpdatedAt: null,
|
|
29
|
+
retryCount: 0,
|
|
30
|
+
isGoingToRetry: false,
|
|
31
|
+
pageParam: undefined,
|
|
32
|
+
pageParams: [undefined],
|
|
33
|
+
hasNextPage: false,
|
|
34
|
+
retryNextPageCount: 0,
|
|
35
|
+
isGoingToRetryNextPage: false,
|
|
36
|
+
};
|
|
37
|
+
const useQueryDefaultDeps = (state) => [
|
|
38
|
+
state.data,
|
|
39
|
+
state.error,
|
|
40
|
+
state.isWaitingNextPage,
|
|
41
|
+
state.hasNextPage,
|
|
42
|
+
];
|
|
43
|
+
export const createQuery = (queryFn, options = {}) => {
|
|
44
|
+
const { onFirstSubscribe = noop, onSubscribe = noop, onLastUnsubscribe = noop, onBeforeChangeKey = noop, defaultDeps = useQueryDefaultDeps, select = identityFn, staleTime = DEFAULT_STALE_TIME, fetchOnMount = true, fetchOnWindowFocus = true, enabled = true, retry = 1, retryDelay = 3000, keepPreviousData, getNextPageParam = () => undefined, onSuccess = noop, onError = noop, onSettled = noop, hashKeyFn = hashStoreKey, ...createStoresOptions } = options;
|
|
45
|
+
const retryTimeoutId = new Map();
|
|
46
|
+
const retryNextPageTimeoutId = new Map();
|
|
47
|
+
const preventReplaceResponse = new Map(); // Prevent optimistic data to be replaced
|
|
48
|
+
const useQuery = createStores(({ key: _key, get, set }) => {
|
|
49
|
+
const key = _key;
|
|
50
|
+
const getRetryProps = (error, retryCount) => {
|
|
51
|
+
const maxRetryCount = (typeof retry === 'function' ? retry(error, key) : retry) || 0;
|
|
52
|
+
const shouldRetry = retryCount < maxRetryCount;
|
|
53
|
+
const delay = (typeof retryDelay === 'function' ? retryDelay(error, key) : retryDelay) || 0;
|
|
54
|
+
return { shouldRetry, delay };
|
|
55
|
+
};
|
|
56
|
+
const forceFetch = () => {
|
|
57
|
+
const responseAllPages = [];
|
|
58
|
+
const newPageParams = [undefined];
|
|
59
|
+
let pageParam = undefined;
|
|
60
|
+
const { isWaiting, isLoading, pageParams } = get();
|
|
61
|
+
if (isWaiting || enabled === false || (typeof enabled === 'function' && !enabled(key)))
|
|
62
|
+
return;
|
|
63
|
+
if (isLoading)
|
|
64
|
+
set({ isWaiting: true });
|
|
65
|
+
else
|
|
66
|
+
set({ isWaiting: true, isRefetching: true });
|
|
67
|
+
const callQuery = () => {
|
|
68
|
+
if (get().isGoingToRetry) {
|
|
69
|
+
if (isLoading)
|
|
70
|
+
set({ isGoingToRetry: false, isWaiting: true });
|
|
71
|
+
else
|
|
72
|
+
set({ isGoingToRetry: false, isWaiting: true, isRefetching: true });
|
|
73
|
+
clearTimeout(retryTimeoutId.get(hashKeyFn(key)));
|
|
74
|
+
}
|
|
75
|
+
const stateBeforeCallQuery = { ...get(), pageParam };
|
|
76
|
+
preventReplaceResponse.set(hashKeyFn(key), false);
|
|
77
|
+
queryFn(key, stateBeforeCallQuery)
|
|
78
|
+
.then((response) => {
|
|
79
|
+
if (preventReplaceResponse.get(hashKeyFn(key))) {
|
|
80
|
+
set({ isWaiting: false });
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
responseAllPages.push(response);
|
|
84
|
+
const newPageParam = getNextPageParam(response, responseAllPages.length);
|
|
85
|
+
newPageParams.push(newPageParam);
|
|
86
|
+
if (newPageParam !== undefined && newPageParams.length < pageParams.length) {
|
|
87
|
+
pageParam = newPageParam;
|
|
88
|
+
callQuery();
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
set({
|
|
92
|
+
isWaiting: false,
|
|
93
|
+
status: 'success',
|
|
94
|
+
isLoading: false,
|
|
95
|
+
isSuccess: true,
|
|
96
|
+
isError: false,
|
|
97
|
+
isRefetching: false,
|
|
98
|
+
isRefetchError: false,
|
|
99
|
+
isPreviousData: false,
|
|
100
|
+
isOptimisticData: false,
|
|
101
|
+
data: responseAllPages.reduce((prev, responseCurrentPage) => {
|
|
102
|
+
return select(responseCurrentPage, { key, data: prev });
|
|
103
|
+
}, null),
|
|
104
|
+
response,
|
|
105
|
+
responseUpdatedAt: Date.now(),
|
|
106
|
+
error: null,
|
|
107
|
+
errorUpdatedAt: null,
|
|
108
|
+
retryCount: 0,
|
|
109
|
+
pageParam: newPageParam,
|
|
110
|
+
pageParams: newPageParams,
|
|
111
|
+
hasNextPage: newPageParam !== undefined,
|
|
112
|
+
});
|
|
113
|
+
onSuccess(response, stateBeforeCallQuery);
|
|
114
|
+
})
|
|
115
|
+
.catch((error) => {
|
|
116
|
+
const prevState = get();
|
|
117
|
+
const errorUpdatedAt = Date.now();
|
|
118
|
+
const { shouldRetry, delay } = getRetryProps(error, prevState.retryCount);
|
|
119
|
+
set(prevState.isSuccess
|
|
120
|
+
? {
|
|
121
|
+
isWaiting: false,
|
|
122
|
+
isRefetching: false,
|
|
123
|
+
isRefetchError: true,
|
|
124
|
+
data: responseAllPages.length
|
|
125
|
+
? responseAllPages.reduce((prev, response) => {
|
|
126
|
+
return select(response, { key, data: prev });
|
|
127
|
+
}, null)
|
|
128
|
+
: prevState.data,
|
|
129
|
+
error,
|
|
130
|
+
errorUpdatedAt,
|
|
131
|
+
isGoingToRetry: shouldRetry,
|
|
132
|
+
pageParam,
|
|
133
|
+
hasNextPage: pageParam !== undefined,
|
|
134
|
+
}
|
|
135
|
+
: {
|
|
136
|
+
isWaiting: false,
|
|
137
|
+
isError: true,
|
|
138
|
+
error,
|
|
139
|
+
errorUpdatedAt,
|
|
140
|
+
isGoingToRetry: shouldRetry,
|
|
141
|
+
pageParam,
|
|
142
|
+
hasNextPage: pageParam !== undefined,
|
|
143
|
+
});
|
|
144
|
+
if (shouldRetry) {
|
|
145
|
+
retryTimeoutId.set(hashKeyFn(key), window.setTimeout(() => {
|
|
146
|
+
set({ retryCount: prevState.retryCount + 1 });
|
|
147
|
+
callQuery();
|
|
148
|
+
}, delay));
|
|
149
|
+
}
|
|
150
|
+
onError(error, stateBeforeCallQuery);
|
|
151
|
+
})
|
|
152
|
+
.finally(() => {
|
|
153
|
+
onSettled(stateBeforeCallQuery);
|
|
154
|
+
});
|
|
155
|
+
};
|
|
156
|
+
callQuery();
|
|
157
|
+
};
|
|
158
|
+
const fetch = () => {
|
|
159
|
+
const { responseUpdatedAt } = get();
|
|
160
|
+
const isStale = Date.now() > Number(responseUpdatedAt) + staleTime;
|
|
161
|
+
if (!isStale)
|
|
162
|
+
return;
|
|
163
|
+
forceFetch();
|
|
164
|
+
};
|
|
165
|
+
const fetchNextPage = () => {
|
|
166
|
+
const state = get();
|
|
167
|
+
const { isLoading, isWaitingNextPage, data, hasNextPage, pageParam, pageParams } = state;
|
|
168
|
+
if (isLoading)
|
|
169
|
+
return forceFetch();
|
|
170
|
+
if (isWaitingNextPage || !hasNextPage)
|
|
171
|
+
return;
|
|
172
|
+
set({ isWaitingNextPage: true, isGoingToRetryNextPage: false });
|
|
173
|
+
clearTimeout(retryNextPageTimeoutId.get(hashKeyFn(key)));
|
|
174
|
+
queryFn(key, { ...state, pageParam })
|
|
175
|
+
.then((response) => {
|
|
176
|
+
const newPageParam = getNextPageParam(response, pageParams.length);
|
|
177
|
+
set({
|
|
178
|
+
isWaitingNextPage: false,
|
|
179
|
+
response,
|
|
180
|
+
responseUpdatedAt: Date.now(),
|
|
181
|
+
data: select(response, { key, data }),
|
|
182
|
+
pageParam: newPageParam,
|
|
183
|
+
pageParams: pageParams.concat(newPageParam),
|
|
184
|
+
hasNextPage: newPageParam !== undefined,
|
|
185
|
+
});
|
|
186
|
+
})
|
|
187
|
+
.catch((error) => {
|
|
188
|
+
const prevState = get();
|
|
189
|
+
const { shouldRetry, delay } = getRetryProps(error, prevState.retryNextPageCount);
|
|
190
|
+
set({
|
|
191
|
+
isWaitingNextPage: false,
|
|
192
|
+
isError: true,
|
|
193
|
+
error,
|
|
194
|
+
errorUpdatedAt: Date.now(),
|
|
195
|
+
isGoingToRetryNextPage: shouldRetry,
|
|
196
|
+
});
|
|
197
|
+
if (shouldRetry) {
|
|
198
|
+
retryNextPageTimeoutId.set(hashKeyFn(key), window.setTimeout(() => {
|
|
199
|
+
set({ retryNextPageCount: prevState.retryNextPageCount + 1 });
|
|
200
|
+
fetchNextPage();
|
|
201
|
+
}, delay));
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
};
|
|
205
|
+
return {
|
|
206
|
+
...INITIAL_QUERY_STATE,
|
|
207
|
+
key,
|
|
208
|
+
fetch,
|
|
209
|
+
forceFetch,
|
|
210
|
+
fetchNextPage,
|
|
211
|
+
reset: () => set(INITIAL_QUERY_STATE),
|
|
212
|
+
optimisticUpdate: (response) => useQuery.optimisticUpdate({ key, response }),
|
|
213
|
+
};
|
|
214
|
+
}, (() => {
|
|
215
|
+
const fetchWindowFocusHandler = () => {
|
|
216
|
+
useQuery.getAllWithSubscriber().forEach((state) => {
|
|
217
|
+
getDecision(fetchOnWindowFocus, state.key, {
|
|
218
|
+
ifTrue: state.fetch,
|
|
219
|
+
ifAlways: state.forceFetch,
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
};
|
|
223
|
+
return {
|
|
224
|
+
...createStoresOptions,
|
|
225
|
+
defaultDeps,
|
|
226
|
+
hashKeyFn,
|
|
227
|
+
onFirstSubscribe: (state) => {
|
|
228
|
+
if (typeof window !== 'undefined' && fetchOnWindowFocus) {
|
|
229
|
+
window.addEventListener('focus', fetchWindowFocusHandler);
|
|
230
|
+
}
|
|
231
|
+
onFirstSubscribe(state);
|
|
232
|
+
},
|
|
233
|
+
onSubscribe: (state) => {
|
|
234
|
+
getDecision(fetchOnMount, state.key, {
|
|
235
|
+
ifTrue: state.fetch,
|
|
236
|
+
ifAlways: state.forceFetch,
|
|
237
|
+
});
|
|
238
|
+
onSubscribe(state);
|
|
239
|
+
},
|
|
240
|
+
onLastUnsubscribe: (state) => {
|
|
241
|
+
if (typeof window !== 'undefined' && fetchOnWindowFocus) {
|
|
242
|
+
window.removeEventListener('focus', fetchWindowFocusHandler);
|
|
243
|
+
}
|
|
244
|
+
useQuery.set(state.key, { retryCount: 0, retryNextPageCount: 0 }, true);
|
|
245
|
+
clearTimeout(retryTimeoutId.get(hashKeyFn(state.key)));
|
|
246
|
+
clearTimeout(retryNextPageTimeoutId.get(hashKeyFn(state.key)));
|
|
247
|
+
onLastUnsubscribe(state);
|
|
248
|
+
},
|
|
249
|
+
onBeforeChangeKey: (nextKey, prevKey) => {
|
|
250
|
+
if (keepPreviousData) {
|
|
251
|
+
const nextData = useQuery.get(nextKey);
|
|
252
|
+
if (!nextData.data) {
|
|
253
|
+
const prevData = useQuery.get(prevKey);
|
|
254
|
+
if (prevData.data) {
|
|
255
|
+
useQuery.set(nextKey, {
|
|
256
|
+
data: prevData.data,
|
|
257
|
+
response: prevData.response,
|
|
258
|
+
isLoading: false,
|
|
259
|
+
isPreviousData: true,
|
|
260
|
+
}, true);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
onBeforeChangeKey(nextKey, prevKey);
|
|
265
|
+
},
|
|
266
|
+
};
|
|
267
|
+
})());
|
|
268
|
+
useQuery.setInitialResponse = ({ key, response }) => {
|
|
269
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
270
|
+
useState(() => {
|
|
271
|
+
if (response === undefined)
|
|
272
|
+
return;
|
|
273
|
+
const newPageParam = getNextPageParam(response, 1);
|
|
274
|
+
useQuery.set(key, {
|
|
275
|
+
status: 'success',
|
|
276
|
+
isLoading: false,
|
|
277
|
+
isSuccess: true,
|
|
278
|
+
isError: false,
|
|
279
|
+
response,
|
|
280
|
+
responseUpdatedAt: Date.now(),
|
|
281
|
+
data: select(response, { key: key, data: null }),
|
|
282
|
+
pageParam: newPageParam,
|
|
283
|
+
pageParams: [undefined, newPageParam],
|
|
284
|
+
hasNextPage: newPageParam !== undefined,
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
};
|
|
288
|
+
useQuery.reset = () => {
|
|
289
|
+
useQuery.getStores().forEach((store) => {
|
|
290
|
+
store.set(INITIAL_QUERY_STATE);
|
|
291
|
+
});
|
|
292
|
+
};
|
|
293
|
+
useQuery.resetSpecificKey = (key) => {
|
|
294
|
+
const store = useQuery.getStore(key);
|
|
295
|
+
store.set(INITIAL_QUERY_STATE);
|
|
296
|
+
};
|
|
297
|
+
useQuery.invalidate = () => {
|
|
298
|
+
useQuery.getStores().forEach((store) => {
|
|
299
|
+
const { set, getSubscribers } = store;
|
|
300
|
+
set({ responseUpdatedAt: null });
|
|
301
|
+
if (getSubscribers().size > 0)
|
|
302
|
+
store.get().forceFetch();
|
|
303
|
+
});
|
|
304
|
+
};
|
|
305
|
+
useQuery.invalidateSpecificKey = (key) => {
|
|
306
|
+
const { set, getSubscribers } = useQuery.getStore(key);
|
|
307
|
+
set({ responseUpdatedAt: null });
|
|
308
|
+
if (getSubscribers().size > 0)
|
|
309
|
+
useQuery.get(key).forceFetch();
|
|
310
|
+
};
|
|
311
|
+
useQuery.optimisticUpdate = ({ key, response }) => {
|
|
312
|
+
const prevState = useQuery.get(key);
|
|
313
|
+
const optimisticResponse = typeof response === 'function'
|
|
314
|
+
? response(prevState)
|
|
315
|
+
: response;
|
|
316
|
+
useQuery.set(key, {
|
|
317
|
+
isOptimisticData: true,
|
|
318
|
+
response: optimisticResponse,
|
|
319
|
+
data: select(optimisticResponse, { key: key, data: null }),
|
|
320
|
+
});
|
|
321
|
+
preventReplaceResponse.set(hashKeyFn(key), true);
|
|
322
|
+
const revert = () => {
|
|
323
|
+
useQuery.set(key, {
|
|
324
|
+
isOptimisticData: false,
|
|
325
|
+
response: prevState.response,
|
|
326
|
+
data: prevState.data,
|
|
327
|
+
});
|
|
328
|
+
};
|
|
329
|
+
const invalidate = () => useQuery.invalidateSpecificKey(key);
|
|
330
|
+
return { revert, invalidate };
|
|
331
|
+
};
|
|
332
|
+
return useQuery;
|
|
333
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { InitStoreOptions, SelectDeps, SetStoreData, StoreData, StoreInitializer, Subscribers } from '../vanilla';
|
|
2
|
+
export type WatchProps<T> = {
|
|
3
|
+
selectDeps?: SelectDeps<T>;
|
|
4
|
+
render: (state: T) => any;
|
|
5
|
+
};
|
|
6
|
+
export type UseStore<T extends StoreData> = {
|
|
7
|
+
/**
|
|
8
|
+
* @param selectDeps A function that return the dependency array (just like in `useEffect`), to trigger reactivity.
|
|
9
|
+
* Defaults to `undefined` (reactive to all state change) if you didn't set `defaultDeps` on `createStore`.
|
|
10
|
+
*
|
|
11
|
+
* IMPORTANT NOTE: `selectDeps` must not be changed after initialization.
|
|
12
|
+
*/
|
|
13
|
+
(selectDeps?: SelectDeps<T>): T;
|
|
14
|
+
get: () => T;
|
|
15
|
+
set: (value: SetStoreData<T>, silent?: boolean) => void;
|
|
16
|
+
subscribe: (fn: (state: T) => void, selectDeps?: SelectDeps<T>) => () => void;
|
|
17
|
+
getSubscribers: () => Subscribers<T>;
|
|
18
|
+
/**
|
|
19
|
+
* Set default values inside a component.
|
|
20
|
+
*
|
|
21
|
+
* IMPORTANT NOTE: Put this on the root component or parent component, before any component subscribed!
|
|
22
|
+
*/
|
|
23
|
+
setDefaultValues: (values: SetStoreData<T>) => void;
|
|
24
|
+
Watch: (props: WatchProps<T>) => any;
|
|
25
|
+
};
|
|
26
|
+
export declare const createStore: <T extends StoreData>(initializer: StoreInitializer<T>, options?: InitStoreOptions<T> & {
|
|
27
|
+
defaultDeps?: SelectDeps<T>;
|
|
28
|
+
}) => UseStore<T>;
|