@pixotope/react-context-store 0.5.0 → 0.7.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/.turbo/turbo-build.log +10 -10
- package/.turbo/turbo-check-types.log +1 -1
- package/.turbo/turbo-test.log +4 -4
- package/CHANGELOG.md +14 -0
- package/README.md +69 -0
- package/dist/index.cjs +42 -73
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +114 -2
- package/dist/index.d.ts +114 -2
- package/dist/index.js +42 -96
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/store.tsx +182 -23
- package/typedoc.json +3 -0
package/src/store.tsx
CHANGED
|
@@ -10,11 +10,21 @@ import { shallowEqual } from "@pixotope/utils/comparison";
|
|
|
10
10
|
|
|
11
11
|
const LIB_NAME = "@pixotope/react-context-store";
|
|
12
12
|
|
|
13
|
+
/**
|
|
14
|
+
* A function used to compare two versions of selected state, to decide
|
|
15
|
+
* whether subscribers should be notified of a change.
|
|
16
|
+
* @param first - The previously selected state.
|
|
17
|
+
* @param second - The newly selected state.
|
|
18
|
+
* @returns `true` if the two values should be treated as equal (no update triggered), `false` otherwise.
|
|
19
|
+
*/
|
|
13
20
|
export type SelectorOptions<Selected> = (
|
|
14
21
|
first: Selected,
|
|
15
22
|
second: Selected
|
|
16
23
|
) => boolean;
|
|
17
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Options that configure the behavior of a store created with {@link createContextStore}.
|
|
27
|
+
*/
|
|
18
28
|
export type ContextOptions = {
|
|
19
29
|
/**
|
|
20
30
|
* If true, the store state will be preserved across mounts and un-mounts
|
|
@@ -29,8 +39,15 @@ export type ContextOptions = {
|
|
|
29
39
|
compare?: SelectorOptions<unknown>;
|
|
30
40
|
};
|
|
31
41
|
|
|
42
|
+
/**
|
|
43
|
+
* The value accepted by a store's `set` function: either the next state
|
|
44
|
+
* directly, or an updater function that receives the previous state and
|
|
45
|
+
* returns the next state.
|
|
46
|
+
*/
|
|
32
47
|
export type SetterArgs<Store> = Store | ((prev: Store) => Store);
|
|
33
|
-
|
|
48
|
+
|
|
49
|
+
/** Maps an actions map (as passed to {@link createContextStore}) to the shape of the object returned by `useActions`. */
|
|
50
|
+
export type ExtractActionKeys<T> = {
|
|
34
51
|
[K in keyof T]: T[K] extends (
|
|
35
52
|
stateProps: never,
|
|
36
53
|
action: infer A
|
|
@@ -43,47 +60,105 @@ type ExtractActionKeys<T> = {
|
|
|
43
60
|
: never;
|
|
44
61
|
};
|
|
45
62
|
|
|
46
|
-
type
|
|
63
|
+
/** Flattens an intersection/mapped type into a single object type, for cleaner hover tooltips. */
|
|
64
|
+
export type Prettify<T> = {
|
|
47
65
|
[K in keyof T]: T[K];
|
|
48
66
|
} & {};
|
|
49
67
|
|
|
68
|
+
/**
|
|
69
|
+
* The action argument passed to an action handler, wrapping its payload.
|
|
70
|
+
* Use this as the type of an action's second parameter to give the
|
|
71
|
+
* corresponding {@link createContextStore} action a typed payload, which is
|
|
72
|
+
* then reflected in the function returned by `useActions`.
|
|
73
|
+
* @typeParam Payload - The type of the action's payload. Defaults to `any`.
|
|
74
|
+
* @example
|
|
75
|
+
* ```tsx
|
|
76
|
+
* const store = createContextStore(initialState, {
|
|
77
|
+
* incrementBy: ({ set, get }, action: ActionablePayload<number>) => {
|
|
78
|
+
* set((prev) => ({ ...prev, count: get().count + action.payload }));
|
|
79
|
+
* },
|
|
80
|
+
* });
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
50
83
|
export type ActionablePayload<Payload = any> = {
|
|
51
84
|
payload: Payload;
|
|
52
85
|
};
|
|
53
86
|
|
|
54
|
-
|
|
87
|
+
/** The state accessors passed as the first argument to every action handler. */
|
|
88
|
+
export type StateActionProps<Store> = {
|
|
89
|
+
/** Updates the store state. */
|
|
55
90
|
set: (value: SetterArgs<Store>) => void;
|
|
91
|
+
/** Returns the current store state. */
|
|
56
92
|
get: () => Store;
|
|
57
93
|
};
|
|
58
94
|
|
|
59
|
-
|
|
95
|
+
/**
|
|
96
|
+
* A map of action names to action handlers, as passed to {@link createContextStore}.
|
|
97
|
+
* Each handler receives {@link StateActionProps} for reading/writing the
|
|
98
|
+
* store, plus an {@link ActionablePayload} carrying any payload passed by the caller.
|
|
99
|
+
*/
|
|
100
|
+
export type Actions<Store> = {
|
|
60
101
|
[key: string]: (
|
|
61
102
|
stateProps: StateActionProps<Store>,
|
|
62
103
|
action: ActionablePayload
|
|
63
104
|
) => void | Promise<void>;
|
|
64
105
|
};
|
|
65
106
|
|
|
66
|
-
|
|
107
|
+
/** The object returned by a store's `useStore` hook. */
|
|
108
|
+
export type UseStoreReturnType<Store, SelectorOutput> = {
|
|
109
|
+
/** The currently selected state. Reading this subscribes the component to updates. */
|
|
67
110
|
state: SelectorOutput;
|
|
111
|
+
/** Returns the currently selected state. Equivalent to reading `state`. */
|
|
68
112
|
get: () => SelectorOutput;
|
|
113
|
+
/** Updates the store state. */
|
|
69
114
|
set: (value: SetterArgs<Store>) => void;
|
|
115
|
+
/** Returns the entire store state, not just the selected slice. */
|
|
70
116
|
selector: () => Store;
|
|
71
117
|
};
|
|
72
118
|
|
|
73
|
-
|
|
119
|
+
/**
|
|
120
|
+
* The object returned by {@link createContextStore}: the `Provider`
|
|
121
|
+
* component plus the hooks and functions used to read, update and
|
|
122
|
+
* subscribe to the store.
|
|
123
|
+
*/
|
|
124
|
+
export type ContextReturnType<Store, A extends Actions<Store>> = {
|
|
125
|
+
/** Wraps the part of the component tree that should have access to the store. */
|
|
74
126
|
Provider: React.FC<React.PropsWithChildren>;
|
|
127
|
+
/**
|
|
128
|
+
* Reads (a selection of) the store state from the nearest `Provider`,
|
|
129
|
+
* re-rendering the component whenever the selected state changes.
|
|
130
|
+
* @param selector - Selects the part of the store the component needs. Defaults to the entire store.
|
|
131
|
+
* @param options - A function used to compare the previous and next selected state. Defaults to the store's `compare` option, or shallow equality.
|
|
132
|
+
* @returns The selected state, plus functions to read and update the store.
|
|
133
|
+
*/
|
|
75
134
|
useStore: <SelectorOutput = Store>(
|
|
76
135
|
selector?: (store: Store) => SelectorOutput,
|
|
77
136
|
options?: SelectorOptions<SelectorOutput>
|
|
78
137
|
) => UseStoreReturnType<Store, SelectorOutput>;
|
|
138
|
+
/**
|
|
139
|
+
* Returns the actions passed to {@link createContextStore}, bound to the
|
|
140
|
+
* current store. Actions declared with an {@link ActionablePayload}
|
|
141
|
+
* parameter take a single payload argument; other actions take none.
|
|
142
|
+
*/
|
|
79
143
|
useActions: () => Prettify<ExtractActionKeys<A>>;
|
|
144
|
+
/**
|
|
145
|
+
* Returns a function to update the store state from the nearest
|
|
146
|
+
* `Provider`, without subscribing the component to store updates.
|
|
147
|
+
*/
|
|
80
148
|
useSetStore: () => (value: SetterArgs<Store>) => void;
|
|
81
|
-
|
|
149
|
+
/**
|
|
150
|
+
* Subscribes to store updates from outside the React component tree.
|
|
151
|
+
* @param selector - Selects the part of the store to watch.
|
|
152
|
+
* @param callback - Called with the selected state whenever it changes.
|
|
153
|
+
* @param options - A function used to compare the previous and next selected state. Defaults to shallow equality.
|
|
154
|
+
* @returns A function to remove the subscription.
|
|
155
|
+
*/
|
|
82
156
|
subscribe: <SelectorOutput = Store>(
|
|
83
157
|
selector: (store: Store) => SelectorOutput,
|
|
84
158
|
callback: (state: SelectorOutput) => void,
|
|
85
159
|
options?: SelectorOptions<SelectorOutput>
|
|
86
160
|
) => () => void;
|
|
161
|
+
/** Removes a subscription created with `subscribe`. */
|
|
87
162
|
unsubscribe: (callback: () => void) => void;
|
|
88
163
|
};
|
|
89
164
|
|
|
@@ -91,6 +166,44 @@ function isFunction(value: any): value is (prev: any) => any {
|
|
|
91
166
|
return typeof value === "function";
|
|
92
167
|
}
|
|
93
168
|
|
|
169
|
+
/**
|
|
170
|
+
* Creates a React context-based store: a `Provider` component plus hooks to
|
|
171
|
+
* read, update and subscribe to its state.
|
|
172
|
+
*
|
|
173
|
+
* Each call creates an independent store with its own React context, so the
|
|
174
|
+
* `Provider` and hooks returned by one call must always be used together.
|
|
175
|
+
* @typeParam Store - The shape of the store state.
|
|
176
|
+
* @typeParam A - The map of named actions available to update the store.
|
|
177
|
+
* @param args - Positional arguments: the `initialState` the store is initialized with, an
|
|
178
|
+
* optional `actions` map of named action handlers used to update the store (see
|
|
179
|
+
* {@link ActionablePayload} for actions that take a payload), and optional `options` to
|
|
180
|
+
* customize the store's behavior.
|
|
181
|
+
* @returns An object with a `Provider` component and hooks to read, update and subscribe to the store's state. See {@link ContextReturnType}.
|
|
182
|
+
* @example
|
|
183
|
+
* ```tsx
|
|
184
|
+
* const store = createContextStore(
|
|
185
|
+
* { count: 0 },
|
|
186
|
+
* {
|
|
187
|
+
* increment: ({ set, get }) => set({ ...get(), count: get().count + 1 }),
|
|
188
|
+
* }
|
|
189
|
+
* );
|
|
190
|
+
*
|
|
191
|
+
* function Counter() {
|
|
192
|
+
* const { state: count } = store.useStore((state) => state.count);
|
|
193
|
+
* const { increment } = store.useActions();
|
|
194
|
+
*
|
|
195
|
+
* return <button onClick={() => increment()}>{count}</button>;
|
|
196
|
+
* }
|
|
197
|
+
*
|
|
198
|
+
* function App() {
|
|
199
|
+
* return (
|
|
200
|
+
* <store.Provider>
|
|
201
|
+
* <Counter />
|
|
202
|
+
* </store.Provider>
|
|
203
|
+
* );
|
|
204
|
+
* }
|
|
205
|
+
* ```
|
|
206
|
+
*/
|
|
94
207
|
export function createContextStore<Store, A extends Actions<Store> = Actions<Store>>(
|
|
95
208
|
...args: Extract<A, { payload: A }> extends { payload: infer Payload }
|
|
96
209
|
? [initialState: Store, actions?: A, options?: ContextOptions]
|
|
@@ -150,8 +263,12 @@ export function createContextStore<Store, A extends Actions<Store> = Actions<Sto
|
|
|
150
263
|
const StoreContext = createContext<UseStoreDataReturnType | null>(null);
|
|
151
264
|
|
|
152
265
|
/**
|
|
153
|
-
*
|
|
154
|
-
*
|
|
266
|
+
* Wraps the part of the component tree that should have access to the
|
|
267
|
+
* store. Must wrap any component using `useStore`, `useActions` or `useSetStore`.
|
|
268
|
+
* @remarks
|
|
269
|
+
* If the store was created with `{ global: true }`, state changes made
|
|
270
|
+
* while a `Provider` is mounted are preserved and restored the next time
|
|
271
|
+
* a `Provider` for this store mounts.
|
|
155
272
|
*/
|
|
156
273
|
function Provider({ children }: React.PropsWithChildren) {
|
|
157
274
|
return (
|
|
@@ -171,6 +288,13 @@ export function createContextStore<Store, A extends Actions<Store> = Actions<Sto
|
|
|
171
288
|
*/
|
|
172
289
|
const observers = new Set<(state: Store) => void>();
|
|
173
290
|
|
|
291
|
+
/**
|
|
292
|
+
* Subscribes to store updates from outside the React component tree.
|
|
293
|
+
* @param selector - Selects the part of the store to watch. Defaults to the whole store.
|
|
294
|
+
* @param callback - Called with the selected state whenever it changes.
|
|
295
|
+
* @param compare - A function used to compare the previous and next selected state. Defaults to shallow equality.
|
|
296
|
+
* @returns A function to remove the subscription.
|
|
297
|
+
*/
|
|
174
298
|
function subscribeExternal<SelectorOutput = Store>(
|
|
175
299
|
selector: (store: Store) => SelectorOutput = (store) =>
|
|
176
300
|
store as unknown as SelectorOutput,
|
|
@@ -220,20 +344,24 @@ export function createContextStore<Store, A extends Actions<Store> = Actions<Sto
|
|
|
220
344
|
} as const;
|
|
221
345
|
|
|
222
346
|
/**
|
|
223
|
-
*
|
|
224
|
-
*
|
|
225
|
-
* @param selector A function that
|
|
226
|
-
*
|
|
227
|
-
* @param
|
|
228
|
-
*
|
|
229
|
-
*
|
|
347
|
+
* Reads (a selection of) the store state from the nearest `Provider`,
|
|
348
|
+
* re-rendering the component whenever the selected state changes.
|
|
349
|
+
* @param selector - A function that selects the part of the store that is
|
|
350
|
+
* needed by the component. Defaults to the entire store.
|
|
351
|
+
* @param compare - A function used to compare the previous and next
|
|
352
|
+
* selected state; the component only re-renders when it returns `false`.
|
|
353
|
+
* Defaults to the store's `compare` option (see {@link ContextOptions}),
|
|
354
|
+
* or `shallowEqual` from `@pixotope/utils/comparison`.
|
|
355
|
+
* @returns An object containing the selected `state`, a `set` function to
|
|
356
|
+
* update the store, a `get` function equivalent to reading `state`, and a
|
|
357
|
+
* `selector` function to read the entire store without selecting.
|
|
230
358
|
* @example
|
|
231
359
|
* ```tsx
|
|
232
|
-
*
|
|
233
|
-
*
|
|
234
|
-
*
|
|
235
|
-
*
|
|
236
|
-
*
|
|
360
|
+
* const { state, set } = store.useStore((store) => store.user);
|
|
361
|
+
* const { state, set } = store.useStore(
|
|
362
|
+
* (store) => store.user,
|
|
363
|
+
* (first, second) => first.id === second.id
|
|
364
|
+
* );
|
|
237
365
|
* ```
|
|
238
366
|
*/
|
|
239
367
|
function useStore<SelectorOutput = Store>(
|
|
@@ -275,6 +403,28 @@ export function createContextStore<Store, A extends Actions<Store> = Actions<Sto
|
|
|
275
403
|
};
|
|
276
404
|
}
|
|
277
405
|
|
|
406
|
+
/**
|
|
407
|
+
* Returns the actions passed to {@link createContextStore}, bound to the
|
|
408
|
+
* current store from the nearest `Provider`. Calling an action does not
|
|
409
|
+
* itself cause a re-render; only the state changes it makes (via `set`)
|
|
410
|
+
* do, and only for components subscribed to the affected state.
|
|
411
|
+
* @returns An object with one bound function per action. Actions declared
|
|
412
|
+
* with an {@link ActionablePayload} parameter take a single payload
|
|
413
|
+
* argument; other actions take no arguments.
|
|
414
|
+
* @example
|
|
415
|
+
* ```tsx
|
|
416
|
+
* const store = createContextStore(initialState, {
|
|
417
|
+
* increment: ({ set, get }) => set({ ...get(), count: get().count + 1 }),
|
|
418
|
+
* incrementBy: ({ set, get }, action: ActionablePayload<number>) =>
|
|
419
|
+
* set({ ...get(), count: get().count + action.payload }),
|
|
420
|
+
* });
|
|
421
|
+
*
|
|
422
|
+
* function Counter() {
|
|
423
|
+
* const { incrementBy } = store.useActions();
|
|
424
|
+
* return <button onClick={() => incrementBy(5)}>+5</button>;
|
|
425
|
+
* }
|
|
426
|
+
* ```
|
|
427
|
+
*/
|
|
278
428
|
function useActions() {
|
|
279
429
|
const store = useContext(StoreContext);
|
|
280
430
|
|
|
@@ -314,15 +464,24 @@ export function createContextStore<Store, A extends Actions<Store> = Actions<Sto
|
|
|
314
464
|
return actionProxy as unknown as ExtractActionKeys<typeof actions>;
|
|
315
465
|
}
|
|
316
466
|
|
|
467
|
+
/**
|
|
468
|
+
* Returns a function to update the store state from the nearest
|
|
469
|
+
* `Provider`, without subscribing the component to store updates.
|
|
470
|
+
* @returns A function that sets the store state, accepting either the
|
|
471
|
+
* next state directly or an updater function that receives the previous state.
|
|
472
|
+
* @example
|
|
473
|
+
* ```tsx
|
|
474
|
+
* const setStore = store.useSetStore();
|
|
475
|
+
* setStore((prev) => ({ ...prev, count: prev.count + 1 }));
|
|
476
|
+
* ```
|
|
477
|
+
*/
|
|
317
478
|
const useSetStore = () => useStore(() => false).set;
|
|
318
|
-
const useStoreSelector = () => useStore(() => false).selector;
|
|
319
479
|
|
|
320
480
|
return {
|
|
321
481
|
Provider,
|
|
322
482
|
useStore,
|
|
323
483
|
useSetStore,
|
|
324
484
|
useActions,
|
|
325
|
-
useStoreSelector,
|
|
326
485
|
subscribe: observable.subscribe,
|
|
327
486
|
unsubscribe: observable.unsubscribe,
|
|
328
487
|
};
|
package/typedoc.json
ADDED