react-redux-cache 0.22.1 → 0.22.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/README.md +32 -28
- package/dist/cjs/createActions.js +63 -63
- package/dist/cjs/createCache.js +216 -131
- package/dist/cjs/createReducer.js +355 -290
- package/dist/cjs/createSelectors.js +66 -58
- package/dist/cjs/index.js +87 -26
- package/dist/cjs/mutate.js +153 -70
- package/dist/cjs/query.js +161 -70
- package/dist/cjs/types.js +2 -2
- package/dist/cjs/useMutation.js +81 -42
- package/dist/cjs/useQuery.js +127 -56
- package/dist/cjs/utilsAndConstants.js +206 -151
- package/dist/esm/createActions.js +60 -59
- package/dist/esm/createCache.js +218 -127
- package/dist/esm/createReducer.js +349 -286
- package/dist/esm/createSelectors.js +63 -54
- package/dist/esm/index.js +3 -3
- package/dist/esm/mutate.js +143 -66
- package/dist/esm/query.js +149 -66
- package/dist/esm/types.js +1 -1
- package/dist/esm/useMutation.js +77 -38
- package/dist/esm/useQuery.js +119 -52
- package/dist/esm/utilsAndConstants.js +192 -140
- package/dist/types/createActions.d.ts +109 -83
- package/dist/types/createCache.d.ts +734 -409
- package/dist/types/createReducer.d.ts +11 -3
- package/dist/types/createSelectors.d.ts +80 -18
- package/dist/types/index.d.ts +3 -3
- package/dist/types/mutate.d.ts +94 -4
- package/dist/types/query.d.ts +123 -4
- package/dist/types/types.d.ts +363 -184
- package/dist/types/useMutation.d.ts +39 -4
- package/dist/types/useQuery.d.ts +40 -4
- package/dist/types/utilsAndConstants.d.ts +56 -23
- package/package.json +7 -8
package/README.md
CHANGED
|
@@ -46,7 +46,7 @@ Can be considered as `ApolloClient` for protocols other than `GraphQL`, but with
|
|
|
46
46
|
```js
|
|
47
47
|
{
|
|
48
48
|
entities: {
|
|
49
|
-
//
|
|
49
|
+
// Each typename has its own map of entities, stored by id.
|
|
50
50
|
users: {
|
|
51
51
|
"0": {id: 0, bankId: "0", name: "User 0 *"},
|
|
52
52
|
"1": {id: 1, bankId: "1", name: "User 1 *"},
|
|
@@ -61,13 +61,13 @@ Can be considered as `ApolloClient` for protocols other than `GraphQL`, but with
|
|
|
61
61
|
}
|
|
62
62
|
},
|
|
63
63
|
queries: {
|
|
64
|
-
//
|
|
64
|
+
// Each query has its own map of query states, stored by cache key, which is generated from query params.
|
|
65
65
|
getUser: {
|
|
66
66
|
"2": {result: 2, params: 2, expiresAt: 1727217298025},
|
|
67
67
|
"3": {loading: Promise<...>, params: 3}
|
|
68
68
|
},
|
|
69
69
|
getUsers: {
|
|
70
|
-
//
|
|
70
|
+
// Example of paginated state under custom cache key.
|
|
71
71
|
"feed": {
|
|
72
72
|
result: {items: [0,1,2], page: 1},
|
|
73
73
|
params: {page: 1}
|
|
@@ -192,7 +192,7 @@ export const {
|
|
|
192
192
|
reducer,
|
|
193
193
|
hooks: {useClient, useMutation, useQuery},
|
|
194
194
|
} = withTypenames<CacheTypenames>().createCache({
|
|
195
|
-
name: 'cache', // Used as prefix for actions and in default cacheStateSelector for selecting cache state from
|
|
195
|
+
name: 'cache', // Used as prefix for actions and in default cacheStateSelector for selecting cache state from the store.
|
|
196
196
|
queries: {
|
|
197
197
|
getUsers: { query: getUsers },
|
|
198
198
|
getUser: {
|
|
@@ -273,15 +273,15 @@ export const getUser = (async (id) => {
|
|
|
273
273
|
return normalize(response, getUserSchema)
|
|
274
274
|
}) satisfies NormalizedQuery<CacheTypenames, number>
|
|
275
275
|
|
|
276
|
-
// Example of query without normalization (not recommended), with selecting access token from the store
|
|
276
|
+
// Example of query without normalization (not recommended), with selecting access token from the store.
|
|
277
277
|
|
|
278
278
|
export const getBank = (async (id, {getState}) => {
|
|
279
279
|
const token = tokenSelector(getState())
|
|
280
280
|
const result: Bank = ...
|
|
281
|
-
return {result} // result is bank object, no entities passed
|
|
281
|
+
return {result} // result is bank object, no entities passed.
|
|
282
282
|
}) satisfies Query<string>
|
|
283
283
|
|
|
284
|
-
// Example of mutation with normalization
|
|
284
|
+
// Example of mutation with normalization.
|
|
285
285
|
|
|
286
286
|
export const removeUser = (async (id, _, abortSignal) => {
|
|
287
287
|
await ...
|
|
@@ -300,18 +300,19 @@ Please check `example/` folder (`npm run example` to run). There are examples fo
|
|
|
300
300
|
export const UserScreen = () => {
|
|
301
301
|
const {id} = useParams()
|
|
302
302
|
|
|
303
|
-
// useQuery
|
|
304
|
-
// Infers all types from created cache
|
|
305
|
-
const [{result:
|
|
303
|
+
// useQuery fetches data if query not cached already (with default FetchPolicy.NoCacheOrExpired).
|
|
304
|
+
// Infers all types from created cache so expects params of type `number`.
|
|
305
|
+
const [{result: user, loading, error}] = useQuery({
|
|
306
306
|
query: 'getUser',
|
|
307
307
|
params: Number(id),
|
|
308
308
|
})
|
|
309
309
|
|
|
310
|
+
// Globally tracks loading state for mutation.
|
|
310
311
|
const [updateUser, {loading: updatingUser}] = useMutation({
|
|
311
312
|
mutation: 'updateUser',
|
|
312
313
|
})
|
|
313
314
|
|
|
314
|
-
// This selector returns entities with proper types - User and Bank
|
|
315
|
+
// This selector used only with normalization and returns entities with proper types - User and Bank.
|
|
315
316
|
const user = useSelectEntityById(userId, 'users')
|
|
316
317
|
const bank = useSelectEntityById(user?.bankId, 'banks')
|
|
317
318
|
|
|
@@ -329,12 +330,12 @@ export const UserScreen = () => {
|
|
|
329
330
|
|
|
330
331
|
For huge collections (> 1000 items, see benchmark) immutable approach may be a bottleneck - every merge of entity, query or mutation state is O(n). There is an option `mutableCollections` that makes it O(1) by using mutable approach when working with collections, while still keeping separate entities, query and mutation states immutable.
|
|
331
332
|
|
|
332
|
-
[Benchmark](https://github.com/gentlee/react-redux-cache/blob/main/benchmark.
|
|
333
|
+
[Benchmark](https://github.com/gentlee/react-redux-cache/blob/main/scripts/benchmark.mjs) results of adding item to collection depending on collection size, in microseconds (Macbook M1, less is better):
|
|
333
334
|
|
|
334
335
|
| Collection size | 0 | 1000 | 10000 | 100000 | 1000000 |
|
|
335
336
|
|-|-|-|-|-|-|
|
|
336
|
-
| immutable | 1.
|
|
337
|
-
| mutable | 1.
|
|
337
|
+
| immutable | 1.57 | 1.81 | 7.62 | 103.82 | 1457.89 |
|
|
338
|
+
| mutable | 1.4 | 1.15 | 0.65 | 1.03 | 0.76 |
|
|
338
339
|
|
|
339
340
|
Well written code should not subcribe to whole collections, so just enabling this options most of the times should not break anything. But if it is still needed, you should subscribe to both collection (it may still change e.g. when clearing state) and to its `_changeKey`.
|
|
340
341
|
|
|
@@ -342,7 +343,7 @@ Well written code should not subcribe to whole collections, so just enabling thi
|
|
|
342
343
|
const Component = () => {
|
|
343
344
|
// It is usually a bad idea to subscribe to whole collections, consider using order of ids and subscribe to a single entity in each cell.
|
|
344
345
|
const allUsers = useSelector((state) => selectEntitiesByTypename(state, 'users'))
|
|
345
|
-
const allUsersChangeKey = useSelector((state) => selectEntitiesByTypename(state, 'users')._changeKey) // <-- Add this line while subscribing to collections
|
|
346
|
+
const allUsersChangeKey = useSelector((state) => selectEntitiesByTypename(state, 'users')._changeKey) // <-- Add this line while subscribing to collections.
|
|
346
347
|
|
|
347
348
|
// For memoized components you should also pass it as extra prop to cause its re-render.
|
|
348
349
|
return <List data={allUsers} extra={allUsersChangeKey}/>
|
|
@@ -366,8 +367,8 @@ export const updateBank = (async (bank) => {
|
|
|
366
367
|
const {httpError, response} = ...
|
|
367
368
|
return {
|
|
368
369
|
result: {
|
|
369
|
-
httpError, // Error is a part of the result, containing e.g. map of not valid fields and threir error messages
|
|
370
|
-
bank: response?.bank // Bank still can be returned from the backend with error e.g. when only some of fields were udpated
|
|
370
|
+
httpError, // Error is a part of the result, containing e.g. map of not valid fields and threir error messages.
|
|
371
|
+
bank: response?.bank // Bank still can be returned from the backend with error e.g. when only some of fields were udpated.
|
|
371
372
|
}
|
|
372
373
|
}
|
|
373
374
|
}) satisfies Mutation<Partial<Bank>>
|
|
@@ -402,7 +403,7 @@ export const cache = createCache({
|
|
|
402
403
|
updateUser: {
|
|
403
404
|
mutation: updateUser,
|
|
404
405
|
onSuccess(_, __, {dispatch}, {invalidateQuery}) {
|
|
405
|
-
// Invalidate getUsers after a single user update (can be done better by updating getUsers state with updateQueryStateAndEntities)
|
|
406
|
+
// Invalidate getUsers after a single user update (can be done better by updating getUsers state with updateQueryStateAndEntities).
|
|
406
407
|
dispatch(invalidateQuery([{query: 'getUsers'}]))
|
|
407
408
|
},
|
|
408
409
|
},
|
|
@@ -425,7 +426,7 @@ export const UserScreen = () => {
|
|
|
425
426
|
const [{loading, error}] = useQuery({
|
|
426
427
|
query: 'getUser',
|
|
427
428
|
params: userId,
|
|
428
|
-
skipFetch: !!user // Disable fetches if we already have user cached by some other query, e.g. getUsers
|
|
429
|
+
skipFetch: !!user // Disable fetches if we already have user cached by some other query, e.g. getUsers.
|
|
429
430
|
})
|
|
430
431
|
|
|
431
432
|
...
|
|
@@ -440,10 +441,10 @@ But if more control is needed, e.g. checking if entity is full, custom fetch pol
|
|
|
440
441
|
query: getUser,
|
|
441
442
|
fetchPolicy(expired, id, _, {getState}, {selectEntityById}) {
|
|
442
443
|
if (expired) {
|
|
443
|
-
return true //
|
|
444
|
+
return true // Fetch if expired.
|
|
444
445
|
}
|
|
445
446
|
|
|
446
|
-
//
|
|
447
|
+
// Fetch if user is not full.
|
|
447
448
|
const user = selectEntityById(getState(), id, 'users')
|
|
448
449
|
return !user || !('name' in user) || !('bankId' in user)
|
|
449
450
|
},
|
|
@@ -465,7 +466,9 @@ export const UserScreen = () => {
|
|
|
465
466
|
|
|
466
467
|
useEffect(() => {
|
|
467
468
|
if (screenIsVisible) {
|
|
468
|
-
|
|
469
|
+
// Expiration happens if expiresAt was set before e.g. by secondsToLive option or invalidateQuery action.
|
|
470
|
+
// If result is not cached yet, it is also considered as expired.
|
|
471
|
+
fetchUser({ onlyIfExpired: true })
|
|
469
472
|
}
|
|
470
473
|
}, [screenIsVisible])
|
|
471
474
|
|
|
@@ -486,7 +489,7 @@ Here is an example of `getUsers` query configuration with pagination support. Yo
|
|
|
486
489
|
queries: {
|
|
487
490
|
getUsers: {
|
|
488
491
|
query: getUsers,
|
|
489
|
-
getCacheKey: () => 'feed', //
|
|
492
|
+
getCacheKey: () => 'feed', // Single cache key is used for all pages.
|
|
490
493
|
mergeResults: (oldResult, {result: newResult}) => {
|
|
491
494
|
if (!oldResult || newResult.page === 1) {
|
|
492
495
|
return newResult
|
|
@@ -549,16 +552,17 @@ export const GetUsersScreen = () => {
|
|
|
549
552
|
Here is a simple `redux-persist` configuration:
|
|
550
553
|
|
|
551
554
|
```typescript
|
|
552
|
-
//
|
|
555
|
+
// Removes `loading` and `error` from persisted state.
|
|
556
|
+
// '_changeKey' is needed only when `mutuableCollections` enabled.
|
|
553
557
|
function stringifyReplacer(key: string, value: unknown) {
|
|
554
|
-
return key === 'loading' || key === 'error' ? undefined : value
|
|
558
|
+
return key === 'loading' || key === 'error' || key === '_changeKey' ? undefined : value
|
|
555
559
|
}
|
|
556
560
|
|
|
557
561
|
const persistedReducer = persistReducer(
|
|
558
562
|
{
|
|
559
563
|
key: 'cache',
|
|
560
564
|
storage,
|
|
561
|
-
whitelist: ['entities', 'queries'], //
|
|
565
|
+
whitelist: ['entities', 'queries'], // Mutation states are ignored.
|
|
562
566
|
throttle: 1000, // ms
|
|
563
567
|
serialize: (value: unknown) => JSON.stringify(value, stringifyReplacer),
|
|
564
568
|
},
|
|
@@ -593,6 +597,6 @@ As example, can be overridden when implementing pagination.
|
|
|
593
597
|
|
|
594
598
|
#### How race conditions are handled?
|
|
595
599
|
|
|
596
|
-
**Queries:** Queries are
|
|
600
|
+
**Queries:** Queries are deduplicated: queries with the same cache key (generated from params by default) use existing fetch promise if already fetching.
|
|
597
601
|
|
|
598
|
-
**Mutations:** Mutations are
|
|
602
|
+
**Mutations:** Mutations are cancelled: previous similar mutation is aborted if it was running when the new one started. Third argument in mutations is `AbortSignal`, which can be used e.g. for cancelling http requests.
|
|
@@ -1,64 +1,64 @@
|
|
|
1
|
-
|
|
2
|
-
Object.defineProperty(exports,
|
|
3
|
-
exports.createActions = void 0
|
|
4
|
-
const utilsAndConstants_1 = require(
|
|
1
|
+
'use strict'
|
|
2
|
+
Object.defineProperty(exports, '__esModule', {value: true})
|
|
3
|
+
exports.createActions = void 0
|
|
4
|
+
const utilsAndConstants_1 = require('./utilsAndConstants')
|
|
5
5
|
const createActions = (name) => {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
64
|
-
exports.createActions = createActions
|
|
6
|
+
const actionPrefix = `@${utilsAndConstants_1.PACKAGE_SHORT_NAME}/${name}/`
|
|
7
|
+
const updateQueryStateAndEntitiesType = `${actionPrefix}updateQueryStateAndEntities`
|
|
8
|
+
const updateQueryStateAndEntities = (queryKey, queryCacheKey, state, entityChanges) => ({
|
|
9
|
+
type: updateQueryStateAndEntitiesType,
|
|
10
|
+
queryKey,
|
|
11
|
+
queryCacheKey,
|
|
12
|
+
state,
|
|
13
|
+
entityChanges,
|
|
14
|
+
})
|
|
15
|
+
updateQueryStateAndEntities.type = updateQueryStateAndEntitiesType
|
|
16
|
+
const updateMutationStateAndEntitiesType = `${actionPrefix}updateMutationStateAndEntities`
|
|
17
|
+
const updateMutationStateAndEntities = (mutationKey, state, entityChanges) => ({
|
|
18
|
+
type: updateMutationStateAndEntitiesType,
|
|
19
|
+
mutationKey,
|
|
20
|
+
state,
|
|
21
|
+
entityChanges,
|
|
22
|
+
})
|
|
23
|
+
updateMutationStateAndEntities.type = updateMutationStateAndEntitiesType
|
|
24
|
+
const mergeEntityChangesType = `${actionPrefix}mergeEntityChanges`
|
|
25
|
+
const mergeEntityChanges = (changes) => ({
|
|
26
|
+
type: mergeEntityChangesType,
|
|
27
|
+
changes,
|
|
28
|
+
})
|
|
29
|
+
mergeEntityChanges.type = mergeEntityChangesType
|
|
30
|
+
const invalidateQueryType = `${actionPrefix}invalidateQuery`
|
|
31
|
+
const invalidateQuery = (queries) => ({
|
|
32
|
+
type: invalidateQueryType,
|
|
33
|
+
queries,
|
|
34
|
+
})
|
|
35
|
+
invalidateQuery.type = invalidateQueryType
|
|
36
|
+
const clearQueryStateType = `${actionPrefix}clearQueryState`
|
|
37
|
+
const clearQueryState = (queries) => ({
|
|
38
|
+
type: clearQueryStateType,
|
|
39
|
+
queries,
|
|
40
|
+
})
|
|
41
|
+
clearQueryState.type = clearQueryStateType
|
|
42
|
+
const clearMutationStateType = `${actionPrefix}clearMutationState`
|
|
43
|
+
const clearMutationState = (mutationKeys) => ({
|
|
44
|
+
type: clearMutationStateType,
|
|
45
|
+
mutationKeys,
|
|
46
|
+
})
|
|
47
|
+
clearMutationState.type = clearMutationStateType
|
|
48
|
+
const clearCacheType = `${actionPrefix}clearCache`
|
|
49
|
+
const clearCache = (stateToKeep) => ({
|
|
50
|
+
type: clearCacheType,
|
|
51
|
+
stateToKeep,
|
|
52
|
+
})
|
|
53
|
+
clearCache.type = clearCacheType
|
|
54
|
+
return {
|
|
55
|
+
updateQueryStateAndEntities,
|
|
56
|
+
updateMutationStateAndEntities,
|
|
57
|
+
mergeEntityChanges,
|
|
58
|
+
invalidateQuery,
|
|
59
|
+
clearQueryState,
|
|
60
|
+
clearMutationState,
|
|
61
|
+
clearCache,
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
exports.createActions = createActions
|