@octanejs/redux-toolkit 0.1.0
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/LICENSE +21 -0
- package/README.md +59 -0
- package/package.json +57 -0
- package/src/dynamicMiddleware/react.ts +97 -0
- package/src/index.ts +3 -0
- package/src/query/index.ts +3 -0
- package/src/query/react/ApiProvider.tsrx +32 -0
- package/src/query/react/ApiProvider.tsrx.d.ts +10 -0
- package/src/query/react/buildHooks.ts +2024 -0
- package/src/query/react/constants.ts +2 -0
- package/src/query/react/index.ts +38 -0
- package/src/query/react/module.ts +247 -0
- package/src/query/react/namedHooks.ts +48 -0
- package/src/query/react/useSerializedStableValue.ts +14 -0
- package/src/query/react/useShallowStableValue.ts +13 -0
- package/src/react/index.ts +5 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { buildCreateApi, coreModule } from '@reduxjs/toolkit/query';
|
|
2
|
+
import { reactHooksModule, reactHooksModuleName } from './module';
|
|
3
|
+
|
|
4
|
+
export * from '@reduxjs/toolkit/query';
|
|
5
|
+
export { ApiProvider } from './ApiProvider.tsrx';
|
|
6
|
+
|
|
7
|
+
const createApi = /* @__PURE__ */ buildCreateApi(coreModule(), reactHooksModule());
|
|
8
|
+
|
|
9
|
+
export type {
|
|
10
|
+
TypedUseMutationResult,
|
|
11
|
+
TypedUseQueryHookResult,
|
|
12
|
+
TypedUseQueryStateResult,
|
|
13
|
+
TypedUseQuerySubscriptionResult,
|
|
14
|
+
TypedLazyQueryTrigger,
|
|
15
|
+
TypedUseLazyQuery,
|
|
16
|
+
TypedUseMutation,
|
|
17
|
+
TypedMutationTrigger,
|
|
18
|
+
TypedQueryStateSelector,
|
|
19
|
+
TypedUseQueryState,
|
|
20
|
+
TypedUseQuery,
|
|
21
|
+
TypedUseQuerySubscription,
|
|
22
|
+
TypedUseLazyQuerySubscription,
|
|
23
|
+
TypedUseQueryStateOptions,
|
|
24
|
+
TypedUseLazyQueryStateResult,
|
|
25
|
+
TypedUseInfiniteQuery,
|
|
26
|
+
TypedUseInfiniteQueryHookResult,
|
|
27
|
+
TypedUseInfiniteQueryStateResult,
|
|
28
|
+
TypedUseInfiniteQuerySubscriptionResult,
|
|
29
|
+
TypedUseInfiniteQueryStateOptions,
|
|
30
|
+
TypedInfiniteQueryStateSelector,
|
|
31
|
+
TypedUseInfiniteQuerySubscription,
|
|
32
|
+
TypedUseInfiniteQueryState,
|
|
33
|
+
TypedLazyInfiniteQueryTrigger,
|
|
34
|
+
TypedUseQuerySubscriptionOptions,
|
|
35
|
+
TypedUseMutationStateOptions,
|
|
36
|
+
} from './buildHooks';
|
|
37
|
+
export { UNINITIALIZED_VALUE } from './constants';
|
|
38
|
+
export { createApi, reactHooksModule, reactHooksModuleName };
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
Api,
|
|
3
|
+
BaseQueryFn,
|
|
4
|
+
EndpointDefinitions,
|
|
5
|
+
InfiniteQueryDefinition,
|
|
6
|
+
Module,
|
|
7
|
+
MutationDefinition,
|
|
8
|
+
PrefetchOptions,
|
|
9
|
+
QueryArgFrom,
|
|
10
|
+
QueryDefinition,
|
|
11
|
+
QueryKeys,
|
|
12
|
+
} from '@reduxjs/toolkit/query';
|
|
13
|
+
import {
|
|
14
|
+
batch as rrBatch,
|
|
15
|
+
useDispatch as rrUseDispatch,
|
|
16
|
+
useSelector as rrUseSelector,
|
|
17
|
+
useStore as rrUseStore,
|
|
18
|
+
} from '@octanejs/redux';
|
|
19
|
+
import type { CreateSelectorFunction } from 'reselect';
|
|
20
|
+
import { createSelector as _createSelector } from 'reselect';
|
|
21
|
+
import type { Dispatch, Store, UnknownAction } from 'redux';
|
|
22
|
+
import type { InfiniteQueryHooks, MutationHooks, QueryHooks } from './buildHooks';
|
|
23
|
+
import { buildHooks } from './buildHooks';
|
|
24
|
+
import type { HooksWithUniqueNames } from './namedHooks';
|
|
25
|
+
|
|
26
|
+
const isQueryDefinition = (definition: { type?: string }) => definition.type === 'query';
|
|
27
|
+
const isMutationDefinition = (definition: { type?: string }) => definition.type === 'mutation';
|
|
28
|
+
const isInfiniteQueryDefinition = (definition: { type?: string }) =>
|
|
29
|
+
definition.type === 'infinitequery';
|
|
30
|
+
const safeAssign = Object.assign;
|
|
31
|
+
const capitalize = (value: string) => value.charAt(0).toUpperCase() + value.slice(1);
|
|
32
|
+
const countObjectKeys = (value: object) => Object.keys(value).length;
|
|
33
|
+
|
|
34
|
+
export const reactHooksModuleName = /* @__PURE__ */ Symbol();
|
|
35
|
+
export type ReactHooksModule = typeof reactHooksModuleName;
|
|
36
|
+
|
|
37
|
+
declare module '@reduxjs/toolkit/query' {
|
|
38
|
+
export interface ApiModules<
|
|
39
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
40
|
+
BaseQuery extends BaseQueryFn,
|
|
41
|
+
Definitions extends EndpointDefinitions,
|
|
42
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
43
|
+
ReducerPath extends string,
|
|
44
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
45
|
+
TagTypes extends string,
|
|
46
|
+
> {
|
|
47
|
+
[reactHooksModuleName]: {
|
|
48
|
+
/**
|
|
49
|
+
* Endpoints based on the input endpoints provided to `createApi`, containing `select`, `hooks` and `action matchers`.
|
|
50
|
+
*/
|
|
51
|
+
endpoints: {
|
|
52
|
+
[K in keyof Definitions]: Definitions[K] extends QueryDefinition<any, any, any, any, any>
|
|
53
|
+
? QueryHooks<Definitions[K]>
|
|
54
|
+
: Definitions[K] extends MutationDefinition<any, any, any, any, any>
|
|
55
|
+
? MutationHooks<Definitions[K]>
|
|
56
|
+
: Definitions[K] extends InfiniteQueryDefinition<any, any, any, any, any>
|
|
57
|
+
? InfiniteQueryHooks<Definitions[K]>
|
|
58
|
+
: never;
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* A hook that accepts a string endpoint name, and provides a callback that when called, pre-fetches the data for that endpoint.
|
|
62
|
+
*/
|
|
63
|
+
usePrefetch<EndpointName extends QueryKeys<Definitions>>(
|
|
64
|
+
endpointName: EndpointName,
|
|
65
|
+
options?: PrefetchOptions,
|
|
66
|
+
): (arg: QueryArgFrom<Definitions[EndpointName]>, options?: PrefetchOptions) => void;
|
|
67
|
+
} & HooksWithUniqueNames<Definitions>;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
type UseDispatch = () => Dispatch<UnknownAction>;
|
|
72
|
+
type UseSelector = <TState = unknown, Selected = unknown>(
|
|
73
|
+
selector: (state: TState) => Selected,
|
|
74
|
+
equalityFnOrOptions?: ((left: Selected, right: Selected) => boolean) | object,
|
|
75
|
+
) => Selected;
|
|
76
|
+
type UseStore = () => Store<any>;
|
|
77
|
+
|
|
78
|
+
export interface ReactHooksModuleOptions {
|
|
79
|
+
/**
|
|
80
|
+
* The hooks from `@octanejs/redux` to be used
|
|
81
|
+
*/
|
|
82
|
+
hooks?: {
|
|
83
|
+
/**
|
|
84
|
+
* The version of the `useDispatch` hook to be used
|
|
85
|
+
*/
|
|
86
|
+
useDispatch: UseDispatch;
|
|
87
|
+
/**
|
|
88
|
+
* The version of the `useSelector` hook to be used
|
|
89
|
+
*/
|
|
90
|
+
useSelector: UseSelector;
|
|
91
|
+
/**
|
|
92
|
+
* The version of the `useStore` hook to be used
|
|
93
|
+
*/
|
|
94
|
+
useStore: UseStore;
|
|
95
|
+
};
|
|
96
|
+
/**
|
|
97
|
+
* The version of the `batchedUpdates` function to be used
|
|
98
|
+
*/
|
|
99
|
+
batch?: (callback: () => void) => void;
|
|
100
|
+
/**
|
|
101
|
+
* Enables performing asynchronous tasks immediately within a render.
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
*
|
|
105
|
+
* ```ts
|
|
106
|
+
* import {
|
|
107
|
+
* buildCreateApi,
|
|
108
|
+
* coreModule,
|
|
109
|
+
* reactHooksModule
|
|
110
|
+
* } from '@octanejs/redux-toolkit/query/react'
|
|
111
|
+
*
|
|
112
|
+
* const createApi = buildCreateApi(
|
|
113
|
+
* coreModule(),
|
|
114
|
+
* reactHooksModule({ unstable__sideEffectsInRender: true })
|
|
115
|
+
* )
|
|
116
|
+
* ```
|
|
117
|
+
*/
|
|
118
|
+
unstable__sideEffectsInRender?: boolean;
|
|
119
|
+
/**
|
|
120
|
+
* A selector creator (usually from `reselect`, or matching the same signature)
|
|
121
|
+
*/
|
|
122
|
+
createSelector?: CreateSelectorFunction<any, any, any>;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Creates a module that generates Octane hooks from endpoints, for use with `buildCreateApi`.
|
|
127
|
+
*
|
|
128
|
+
* @example
|
|
129
|
+
* ```ts
|
|
130
|
+
* const MyContext = createContext<ReactReduxContextValue | null>(null);
|
|
131
|
+
* const customCreateApi = buildCreateApi(
|
|
132
|
+
* coreModule(),
|
|
133
|
+
* reactHooksModule({
|
|
134
|
+
* hooks: {
|
|
135
|
+
* useDispatch: createDispatchHook(MyContext),
|
|
136
|
+
* useSelector: createSelectorHook(MyContext),
|
|
137
|
+
* useStore: createStoreHook(MyContext)
|
|
138
|
+
* }
|
|
139
|
+
* })
|
|
140
|
+
* );
|
|
141
|
+
* ```
|
|
142
|
+
*
|
|
143
|
+
* @returns A module for use with `buildCreateApi`
|
|
144
|
+
*/
|
|
145
|
+
export const reactHooksModule = ({
|
|
146
|
+
batch = rrBatch,
|
|
147
|
+
hooks = {
|
|
148
|
+
useDispatch: rrUseDispatch as UseDispatch,
|
|
149
|
+
useSelector: rrUseSelector as UseSelector,
|
|
150
|
+
useStore: rrUseStore as UseStore,
|
|
151
|
+
},
|
|
152
|
+
createSelector = _createSelector,
|
|
153
|
+
unstable__sideEffectsInRender = false,
|
|
154
|
+
...rest
|
|
155
|
+
}: ReactHooksModuleOptions = {}): Module<ReactHooksModule> => {
|
|
156
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
157
|
+
const hookNames = ['useDispatch', 'useSelector', 'useStore'] as const;
|
|
158
|
+
let warned = false;
|
|
159
|
+
for (const hookName of hookNames) {
|
|
160
|
+
// warn for old hook options
|
|
161
|
+
if (countObjectKeys(rest) > 0) {
|
|
162
|
+
if ((rest as Partial<typeof hooks>)[hookName]) {
|
|
163
|
+
if (!warned) {
|
|
164
|
+
console.warn(
|
|
165
|
+
'As of RTK 2.0, the hooks now need to be specified as one object, provided under a `hooks` key:' +
|
|
166
|
+
'\n`reactHooksModule({ hooks: { useDispatch, useSelector, useStore } })`',
|
|
167
|
+
);
|
|
168
|
+
warned = true;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
// migrate
|
|
172
|
+
// @ts-ignore
|
|
173
|
+
hooks[hookName] = rest[hookName];
|
|
174
|
+
}
|
|
175
|
+
// then make sure we have them all
|
|
176
|
+
if (typeof hooks[hookName] !== 'function') {
|
|
177
|
+
throw new Error(
|
|
178
|
+
`When using custom hooks for context, all ${
|
|
179
|
+
hookNames.length
|
|
180
|
+
} hooks need to be provided: ${hookNames.join(
|
|
181
|
+
', ',
|
|
182
|
+
)}.\nHook ${hookName} was either not provided or not a function.`,
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return {
|
|
189
|
+
name: reactHooksModuleName,
|
|
190
|
+
init(api, { serializeQueryArgs }, context) {
|
|
191
|
+
const anyApi = api as any as Api<any, Record<string, any>, any, any, ReactHooksModule>;
|
|
192
|
+
const { buildQueryHooks, buildInfiniteQueryHooks, buildMutationHook, usePrefetch } =
|
|
193
|
+
buildHooks({
|
|
194
|
+
api,
|
|
195
|
+
moduleOptions: {
|
|
196
|
+
batch,
|
|
197
|
+
hooks,
|
|
198
|
+
unstable__sideEffectsInRender,
|
|
199
|
+
createSelector,
|
|
200
|
+
},
|
|
201
|
+
serializeQueryArgs,
|
|
202
|
+
context,
|
|
203
|
+
});
|
|
204
|
+
safeAssign(anyApi, { usePrefetch });
|
|
205
|
+
safeAssign(context, { batch });
|
|
206
|
+
|
|
207
|
+
return {
|
|
208
|
+
injectEndpoint(endpointName, definition) {
|
|
209
|
+
if (isQueryDefinition(definition)) {
|
|
210
|
+
const {
|
|
211
|
+
useQuery,
|
|
212
|
+
useLazyQuery,
|
|
213
|
+
useLazyQuerySubscription,
|
|
214
|
+
useQueryState,
|
|
215
|
+
useQuerySubscription,
|
|
216
|
+
} = buildQueryHooks(endpointName);
|
|
217
|
+
safeAssign(anyApi.endpoints[endpointName], {
|
|
218
|
+
useQuery,
|
|
219
|
+
useLazyQuery,
|
|
220
|
+
useLazyQuerySubscription,
|
|
221
|
+
useQueryState,
|
|
222
|
+
useQuerySubscription,
|
|
223
|
+
});
|
|
224
|
+
(api as any)[`use${capitalize(endpointName)}Query`] = useQuery;
|
|
225
|
+
(api as any)[`useLazy${capitalize(endpointName)}Query`] = useLazyQuery;
|
|
226
|
+
}
|
|
227
|
+
if (isMutationDefinition(definition)) {
|
|
228
|
+
const useMutation = buildMutationHook(endpointName);
|
|
229
|
+
safeAssign(anyApi.endpoints[endpointName], {
|
|
230
|
+
useMutation,
|
|
231
|
+
});
|
|
232
|
+
(api as any)[`use${capitalize(endpointName)}Mutation`] = useMutation;
|
|
233
|
+
} else if (isInfiniteQueryDefinition(definition)) {
|
|
234
|
+
const { useInfiniteQuery, useInfiniteQuerySubscription, useInfiniteQueryState } =
|
|
235
|
+
buildInfiniteQueryHooks(endpointName);
|
|
236
|
+
safeAssign(anyApi.endpoints[endpointName], {
|
|
237
|
+
useInfiniteQuery,
|
|
238
|
+
useInfiniteQuerySubscription,
|
|
239
|
+
useInfiniteQueryState,
|
|
240
|
+
});
|
|
241
|
+
(api as any)[`use${capitalize(endpointName)}InfiniteQuery`] = useInfiniteQuery;
|
|
242
|
+
}
|
|
243
|
+
},
|
|
244
|
+
};
|
|
245
|
+
},
|
|
246
|
+
};
|
|
247
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
DefinitionType,
|
|
3
|
+
EndpointDefinitions,
|
|
4
|
+
MutationDefinition,
|
|
5
|
+
QueryDefinition,
|
|
6
|
+
InfiniteQueryDefinition,
|
|
7
|
+
} from '@reduxjs/toolkit/query';
|
|
8
|
+
import type { UseInfiniteQuery, UseLazyQuery, UseMutation, UseQuery } from './buildHooks';
|
|
9
|
+
|
|
10
|
+
type QueryHookNames<Definitions extends EndpointDefinitions> = {
|
|
11
|
+
[K in keyof Definitions as Definitions[K] extends {
|
|
12
|
+
type: DefinitionType.query;
|
|
13
|
+
}
|
|
14
|
+
? `use${Capitalize<K & string>}Query`
|
|
15
|
+
: never]: UseQuery<Extract<Definitions[K], QueryDefinition<any, any, any, any>>>;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
type LazyQueryHookNames<Definitions extends EndpointDefinitions> = {
|
|
19
|
+
[K in keyof Definitions as Definitions[K] extends {
|
|
20
|
+
type: DefinitionType.query;
|
|
21
|
+
}
|
|
22
|
+
? `useLazy${Capitalize<K & string>}Query`
|
|
23
|
+
: never]: UseLazyQuery<Extract<Definitions[K], QueryDefinition<any, any, any, any>>>;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
type InfiniteQueryHookNames<Definitions extends EndpointDefinitions> = {
|
|
27
|
+
[K in keyof Definitions as Definitions[K] extends {
|
|
28
|
+
type: DefinitionType.infinitequery;
|
|
29
|
+
}
|
|
30
|
+
? `use${Capitalize<K & string>}InfiniteQuery`
|
|
31
|
+
: never]: UseInfiniteQuery<
|
|
32
|
+
Extract<Definitions[K], InfiniteQueryDefinition<any, any, any, any, any>>
|
|
33
|
+
>;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
type MutationHookNames<Definitions extends EndpointDefinitions> = {
|
|
37
|
+
[K in keyof Definitions as Definitions[K] extends {
|
|
38
|
+
type: DefinitionType.mutation;
|
|
39
|
+
}
|
|
40
|
+
? `use${Capitalize<K & string>}Mutation`
|
|
41
|
+
: never]: UseMutation<Extract<Definitions[K], MutationDefinition<any, any, any, any>>>;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export type HooksWithUniqueNames<Definitions extends EndpointDefinitions> =
|
|
45
|
+
QueryHookNames<Definitions> &
|
|
46
|
+
LazyQueryHookNames<Definitions> &
|
|
47
|
+
InfiniteQueryHookNames<Definitions> &
|
|
48
|
+
MutationHookNames<Definitions>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { useEffect, useRef, useMemo } from 'octane';
|
|
2
|
+
import { copyWithStructuralSharing } from '@reduxjs/toolkit/query';
|
|
3
|
+
|
|
4
|
+
export function useStableQueryArgs<T>(queryArgs: T) {
|
|
5
|
+
const cache = useRef(queryArgs);
|
|
6
|
+
const copy = useMemo(() => copyWithStructuralSharing(cache.current, queryArgs), [queryArgs]);
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
if (cache.current !== copy) {
|
|
9
|
+
cache.current = copy;
|
|
10
|
+
}
|
|
11
|
+
}, [copy]);
|
|
12
|
+
|
|
13
|
+
return copy;
|
|
14
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'octane';
|
|
2
|
+
import { shallowEqual } from '@octanejs/redux';
|
|
3
|
+
|
|
4
|
+
export function useShallowStableValue<T>(value: T) {
|
|
5
|
+
const cache = useRef(value);
|
|
6
|
+
useEffect(() => {
|
|
7
|
+
if (!shallowEqual(cache.current, value)) {
|
|
8
|
+
cache.current = value;
|
|
9
|
+
}
|
|
10
|
+
}, [value]);
|
|
11
|
+
|
|
12
|
+
return shallowEqual(cache.current, value) ? cache.current : value;
|
|
13
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// Compatibility counterpart of `@reduxjs/toolkit/react`: the root Toolkit
|
|
2
|
+
// surface plus the dynamic-middleware helper bound to @octanejs/redux.
|
|
3
|
+
export * from '@reduxjs/toolkit';
|
|
4
|
+
export { createDynamicMiddleware } from '../dynamicMiddleware/react';
|
|
5
|
+
export type { CreateDispatchWithMiddlewareHook } from '../dynamicMiddleware/react';
|