react-hooks-global-states 1.0.7 → 2.0.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/README.md +123 -78
- package/lib/bundle.js +1304 -2
- package/lib/bundle.js.map +1 -0
- package/lib/src/GlobalStore.combiners.d.ts +12 -0
- package/lib/src/GlobalStore.context.d.ts +1 -1
- package/lib/src/GlobalStore.d.ts +39 -39
- package/lib/src/GlobalStore.functionHooks.d.ts +8 -6
- package/lib/src/GlobalStore.types.d.ts +19 -17
- package/lib/src/GlobalStoreAbstract.d.ts +8 -8
- package/package.json +1 -1
- package/lib/bundle.js.LICENSE.txt +0 -1
package/README.md
CHANGED
|
@@ -58,15 +58,67 @@ Now, let's say we want to have a filter bar for the contacts that will only have
|
|
|
58
58
|
|
|
59
59
|
**FilterBar.tsx**
|
|
60
60
|
|
|
61
|
-
```
|
|
62
|
-
const [
|
|
63
|
-
|
|
64
|
-
return
|
|
61
|
+
```tsx
|
|
62
|
+
const [contacts] = useContacts((state) => state.contacts.filter((contact) => contact.status === 'active'));
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<ul>
|
|
66
|
+
{contacts.map((contact) => (
|
|
67
|
+
<li key={contact.id}>{contact.name}</li>
|
|
68
|
+
))}
|
|
69
|
+
</ul>
|
|
70
|
+
);
|
|
65
71
|
```
|
|
66
72
|
|
|
67
73
|
There you have it again, super simple! By adding a **selector** function, you are able to create a derivative hook that will only trigger when the result of the **selector** changes.
|
|
68
74
|
|
|
69
|
-
|
|
75
|
+
If you want to have more control over when the hook should recompute the selector result, there are a couple of options:
|
|
76
|
+
|
|
77
|
+
```tsx
|
|
78
|
+
const [filter, setFilter] = useState('');
|
|
79
|
+
|
|
80
|
+
const [contacts] = useContacts((state) => state.contacts.filter((contact) => contact.name.includes(filter)), {
|
|
81
|
+
/**
|
|
82
|
+
* You can use the `isEqualRoot` to validate if the values before the selector are equal.
|
|
83
|
+
* This validation will run before `isEqual` and if the result is true the selector will not be recomputed.
|
|
84
|
+
* If the result is true the re-render of the component will be prevented.
|
|
85
|
+
*/
|
|
86
|
+
isEqualRoot: (r1, r2) => r1.filter === r2.filter,
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* You can use the `isEqual` to validate if the values after the selector are equal.
|
|
90
|
+
* This validation will run after the selector computed a new value...
|
|
91
|
+
* and if the result is true it will prevent the re-render of the component.
|
|
92
|
+
*/
|
|
93
|
+
isEqual: (filter1, filter2) => filter1 === filter2,
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* You can use the `dependencies` array as with regular hooks to to force the recomputation of the selector.
|
|
97
|
+
* Is important ot mention that changes in the dependencies will not trigger a re-render of the component...
|
|
98
|
+
* Instead the recomputation of the selector will returned immediately.
|
|
99
|
+
*/
|
|
100
|
+
dependencies: [filter],
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
return (
|
|
104
|
+
<ul>
|
|
105
|
+
{contacts.map((contact) => (
|
|
106
|
+
<li key={contact.id}>{contact.name}</li>
|
|
107
|
+
))}
|
|
108
|
+
</ul>
|
|
109
|
+
);
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
If you want to perform a shallow comparison between the previous and new values, you can use the **shallowCompare** function from the library.
|
|
113
|
+
|
|
114
|
+
```TSX
|
|
115
|
+
({
|
|
116
|
+
/**
|
|
117
|
+
* You can use the `shallowCompare` from the GlobalStore.utils to compare the values at first level.
|
|
118
|
+
*/
|
|
119
|
+
isEqual: shallowCompare,
|
|
120
|
+
})
|
|
121
|
+
```
|
|
70
122
|
|
|
71
123
|
## What if you want to reuse the selector?
|
|
72
124
|
|
|
@@ -88,7 +140,7 @@ const [{ filter }, setState] = useFilter();
|
|
|
88
140
|
return <TextInput onChangeText={() => setState((state) => ({ ...state, filter }))} />;
|
|
89
141
|
```
|
|
90
142
|
|
|
91
|
-
Notice that the **state** changes, but the **
|
|
143
|
+
Notice that the **state** changes, but the **stateMutator** does not. This is because this is a **DERIVATE state**, and it cannot be directly changed. It will always be derived from the main hook.
|
|
92
144
|
|
|
93
145
|
# State actions
|
|
94
146
|
|
|
@@ -125,7 +177,7 @@ export const useContacts = createGlobalState(initialState, {
|
|
|
125
177
|
});
|
|
126
178
|
```
|
|
127
179
|
|
|
128
|
-
That's it! In this updated version, the **useContacts** hook will no longer return [**state**, **
|
|
180
|
+
That's it! In this updated version, the **useContacts** hook will no longer return [**state**, **stateMutator:Setter<State>**] but instead will return [**state**, **stateMutator:ActionCollectionResult<State>**]. This change will provide a more intuitive and convenient way to access and interact with the state and its associated actions.
|
|
129
181
|
|
|
130
182
|
Let's see how that will look now into our **FilterBar.tsx**
|
|
131
183
|
|
|
@@ -155,41 +207,52 @@ It can't get any simpler, right? Everything is connected, everything is reactive
|
|
|
155
207
|
|
|
156
208
|
# Decoupled state access
|
|
157
209
|
|
|
158
|
-
If you need to access the global state outside of a component or a hook without subscribing to state changes, or even inside a **ClassComponent**, you can use
|
|
210
|
+
If you need to access the global state outside of a component or a hook without subscribing to state changes, or even inside a **ClassComponent**, you can use:
|
|
211
|
+
|
|
212
|
+
```tsx
|
|
213
|
+
GlobalStateHook.stateControls: () => [stateRetriever: StateGetter<State>, stateMutator: Setter<State>|ActionCollectionResult<State>];
|
|
214
|
+
|
|
215
|
+
// example:
|
|
216
|
+
const [getContacts, setContacts] = useContacts.stateControls();
|
|
217
|
+
|
|
218
|
+
console.log(getContacts()); // prints the list of contacts
|
|
219
|
+
```
|
|
159
220
|
|
|
160
|
-
|
|
221
|
+
**stateMutator** is particularly useful when you want to create components that have editing access to a specific store but don't necessarily need to reactively respond to state changes.
|
|
161
222
|
|
|
162
|
-
Using
|
|
223
|
+
Using the **stateRetriever** and the **stateMutator** allows you to retrieve the state when needed without establishing a reactive relationship with the state changes. This approach provides more flexibility and control over when and how components interact with the global state. Let's see and example:
|
|
163
224
|
|
|
164
225
|
```ts
|
|
165
|
-
import {
|
|
226
|
+
import { createGlobalState } from 'react-hooks-global-states';
|
|
166
227
|
|
|
167
|
-
export const
|
|
228
|
+
export const useContacts = createGlobalState({
|
|
168
229
|
isLoading: true,
|
|
169
230
|
filter: '',
|
|
170
231
|
items: [] as Contact[],
|
|
171
232
|
});
|
|
233
|
+
|
|
234
|
+
export const [contactsRetriever, contactsMutator] = useContacts.stateControls();
|
|
172
235
|
```
|
|
173
236
|
|
|
174
|
-
That's great! With the addition of the **
|
|
237
|
+
That's great! With the addition of the **contactsRetriever** and **contactsMutator** methods, you now have the ability to access and modify the state without the need for subscription to the hook.
|
|
175
238
|
|
|
176
|
-
While **useContacts** will allow your components to subscribe to the custom hook, using the **
|
|
239
|
+
While **useContacts** will allow your components to subscribe to the custom hook, using the **contactsRetriever** method you will be able retrieve the current value of the state. This allows you to access the state whenever necessary, without being reactive to its changes. Let' see how:
|
|
177
240
|
|
|
178
241
|
```ts
|
|
179
242
|
// To synchronously get the value of the state
|
|
180
|
-
const value =
|
|
243
|
+
const value = contactsRetriever();
|
|
181
244
|
|
|
182
245
|
// the type of value will be { isLoading: boolean; filter: string; items: Contact[] }
|
|
183
246
|
```
|
|
184
247
|
|
|
185
|
-
Additionally, to subscribe to state changes, you can pass a callback function as a parameter to the **
|
|
248
|
+
Additionally, to subscribe to state changes, you can pass a callback function as a parameter to the **stateRetriever**. This approach enables you to create a subscription group, allowing you to subscribe to either the entire state or a specific portion of it. When a callback function is provided to the **stateRetriever**, it will return a cleanup function instead of the state. This cleanup function can be used to unsubscribe or clean up the subscription when it is no longer needed.
|
|
186
249
|
|
|
187
250
|
```ts
|
|
188
251
|
/**
|
|
189
252
|
* This not only allows you to retrieve the current value of the state...
|
|
190
253
|
* but also enables you to subscribe to any changes in the state or a portion of it
|
|
191
254
|
*/
|
|
192
|
-
const removeSubscriptionGroup =
|
|
255
|
+
const removeSubscriptionGroup = contactsRetriever<Subscribe>((subscribe) => {
|
|
193
256
|
subscribe((state) => {
|
|
194
257
|
console.log('state changed: ', state);
|
|
195
258
|
});
|
|
@@ -210,12 +273,12 @@ That's great, isn't it? everything stays synchronized with the original state!!
|
|
|
210
273
|
So, we have seen that we can subscribe a callback to state changes, create **derivative states** from our global hooks, **and derive hooks from those derivative states**. Guess what? We can also create derivative **emitters** and subscribe callbacks to specific portions of the state. Let's review it:
|
|
211
274
|
|
|
212
275
|
```ts
|
|
213
|
-
const subscribeToFilter = createDerivateEmitter(
|
|
276
|
+
const subscribeToFilter = createDerivateEmitter(contactsRetriever, ({ filter }) => ({
|
|
214
277
|
filter,
|
|
215
278
|
}));
|
|
216
279
|
```
|
|
217
280
|
|
|
218
|
-
Cool, it's basically the same, but instead of using the **hook** as a parameter, we just have to use the **
|
|
281
|
+
Cool, it's basically the same, but instead of using the **hook** as a parameter, we just have to use the **stateRetriever** as a parameter, and that will make the magic.
|
|
219
282
|
|
|
220
283
|
Now we are able to add a callback that will be executed every time the state of the **filter** changes.
|
|
221
284
|
|
|
@@ -264,55 +327,59 @@ const removeFilterSubscription = subscribeToFilter<Subscribe>(
|
|
|
264
327
|
And guess what again? You can also derive emitters from derived emitters without any trouble at all! It works basically the same. Let's see an example:
|
|
265
328
|
|
|
266
329
|
```ts
|
|
267
|
-
const subscribeToItems = createDerivateEmitter(
|
|
330
|
+
const subscribeToItems = createDerivateEmitter(contactsRetriever, ({ items }) => items);
|
|
268
331
|
|
|
269
332
|
const subscribeToItemsLength = createDerivateEmitter(subscribeToItems, (items) => items.length);
|
|
270
333
|
```
|
|
271
334
|
|
|
272
335
|
The examples may seem a little silly, but they allow you to see the incredible things you can accomplish with these **derivative states** and **emitters**. They open up a world of possibilities!
|
|
273
336
|
|
|
274
|
-
# Combining
|
|
337
|
+
# Combining stateRetriever
|
|
275
338
|
|
|
276
|
-
What if you have two states and you want to combine them? You may have already guessed it right? ... you can create combined **emitters** and **hooks** from the hook **
|
|
339
|
+
What if you have two states and you want to combine them? You may have already guessed it right? ... you can create combined **emitters** and **hooks** from the hook **stateRetriever**.
|
|
277
340
|
|
|
278
341
|
By utilizing the approach of combining **emitters** and **hooks**, you can effectively merge multiple states and make them shareable. This allows for better organization and simplifies the management of the combined states. You don't need to refactor everything; you just need to combine the **global state hooks** you already have. Let's see a simple example:
|
|
279
342
|
|
|
280
|
-
|
|
343
|
+
First we are gonna create a couple of **global states**, and extract the **stateRetriever**. (In case you are using an instance of **GlobalStore** or **GlobalStoreAbstract** you can just pick up the stateRetrievers from the **getHookDecoupled** method)
|
|
281
344
|
|
|
282
345
|
```ts
|
|
283
|
-
const
|
|
346
|
+
const useHook1 = createGlobalState({
|
|
284
347
|
propA: 1,
|
|
285
348
|
propB: 2,
|
|
286
349
|
});
|
|
287
350
|
|
|
288
|
-
const [,
|
|
351
|
+
const [stateRetriever1, stateMutator1] = useHook1.stateControls();
|
|
352
|
+
|
|
353
|
+
const useHook2 = createGlobalState({
|
|
289
354
|
propC: 3,
|
|
290
355
|
propD: 4,
|
|
291
356
|
});
|
|
357
|
+
|
|
358
|
+
const [, stateRetriever2] = useHook2.stateControls();
|
|
292
359
|
```
|
|
293
360
|
|
|
294
361
|
Okay, cool, the first state as **propA, propB** while the second one has **propC, propD**, let's combine them:
|
|
295
362
|
|
|
296
363
|
```ts
|
|
297
|
-
const [useCombinedHook,
|
|
364
|
+
const [useCombinedHook, stateRetriever, dispose] = combineAsyncGetters(
|
|
298
365
|
{
|
|
299
366
|
selector: ([state1, state2]) => ({
|
|
300
367
|
...state1,
|
|
301
368
|
...state2,
|
|
302
369
|
}),
|
|
303
370
|
},
|
|
304
|
-
|
|
305
|
-
|
|
371
|
+
stateRetriever1,
|
|
372
|
+
stateRetriever2
|
|
306
373
|
);
|
|
307
374
|
```
|
|
308
375
|
|
|
309
|
-
Well, that's it! Now you have access to a **
|
|
376
|
+
Well, that's it! Now you have access to a **stateRetriever** that will return the combined value of the two states. From this new **stateRetriever**, you can retrieve the value or subscribe to its changes. Let'see:
|
|
310
377
|
|
|
311
378
|
```ts
|
|
312
|
-
const value =
|
|
379
|
+
const value = stateRetriever(); // { propA, propB, propC, propD }
|
|
313
380
|
|
|
314
381
|
// subscribe to the new emitter
|
|
315
|
-
const unsubscribeGroup =
|
|
382
|
+
const unsubscribeGroup = stateRetriever<Subscribe>((subscribe) => {
|
|
316
383
|
subscribe((state) => {
|
|
317
384
|
console.log(subscribe); // full state
|
|
318
385
|
});
|
|
@@ -344,34 +411,38 @@ Similar to your other **global state hooks**, **combined hooks** allow you to us
|
|
|
344
411
|
const [fragment] = useCombinedHook(({ propA, propD }) => ({ propA, propD }));
|
|
345
412
|
```
|
|
346
413
|
|
|
347
|
-
Lastly, you have the flexibility to continue combining
|
|
414
|
+
Lastly, you have the flexibility to continue combining stateRetrievers if desired. This means you can extend the functionality of combined hooks by adding more stateRetrievers to merge additional states. By combining stateRetrievers in this way, you can create a comprehensive and unified representation of the combined states within your application. This approach allows for modular and scalable state management, enabling you to efficiently handle complex state compositions.
|
|
348
415
|
|
|
349
416
|
Let's see an example:
|
|
350
417
|
|
|
351
418
|
```ts
|
|
352
|
-
const [useCombinedHook,
|
|
419
|
+
const [useCombinedHook, combinedStateRetriever1, dispose1] = combineAsyncGetters(
|
|
353
420
|
{
|
|
354
421
|
selector: ([state1, state2]) => ({
|
|
355
422
|
...state1,
|
|
356
423
|
...state2,
|
|
357
424
|
}),
|
|
358
425
|
},
|
|
359
|
-
|
|
360
|
-
|
|
426
|
+
stateRetriever1,
|
|
427
|
+
stateRetriever2
|
|
361
428
|
);
|
|
362
429
|
|
|
363
|
-
const
|
|
430
|
+
const useHook3 = createGlobalState({
|
|
364
431
|
propE: 1,
|
|
365
432
|
propF: 2,
|
|
366
433
|
});
|
|
367
434
|
|
|
368
|
-
const [
|
|
435
|
+
const [stateRetriever3, stateMutator3] = useHook3.stateControls();
|
|
436
|
+
|
|
437
|
+
const useIsLoading = createGlobalState(false);
|
|
438
|
+
|
|
439
|
+
const [isLoadingStateRetriever, isLoadingMutator] = useIsLoading.stateControls();
|
|
369
440
|
```
|
|
370
441
|
|
|
371
442
|
Once we created another peace of state, we can combine it with our other **global hooks** and **emitters**
|
|
372
443
|
|
|
373
444
|
```ts
|
|
374
|
-
const [useCombinedHook2,
|
|
445
|
+
const [useCombinedHook2, combinedStateRetriever2, dispose2] = combineAsyncGetters(
|
|
375
446
|
{
|
|
376
447
|
selector: ([state1, state2, isLoading]) => ({
|
|
377
448
|
...state1,
|
|
@@ -379,9 +450,9 @@ const [useCombinedHook2, combinedGetter2, dispose2] = combineAsyncGetters(
|
|
|
379
450
|
isLoading,
|
|
380
451
|
}),
|
|
381
452
|
},
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
453
|
+
combinedStateRetriever1,
|
|
454
|
+
stateRetriever3,
|
|
455
|
+
isLoadingStateRetriever
|
|
385
456
|
);
|
|
386
457
|
```
|
|
387
458
|
|
|
@@ -389,11 +460,11 @@ You have the freedom to combine as many global hooks as you wish. This means you
|
|
|
389
460
|
|
|
390
461
|
### **Quick note**:
|
|
391
462
|
|
|
392
|
-
Please be aware that the third parameter is a **dispose callback**, which can be particularly useful in **
|
|
463
|
+
Please be aware that the third parameter is a **dispose callback**, which can be particularly useful in **higher-order** functions when you want to release any resources associated with the hook. By invoking the dispose callback, the hook will no longer report any changes, ensuring that resources are properly cleaned up. This allows for efficient resource management and can be beneficial in scenarios where you need to handle resource cleanup or termination in a controlled manner.
|
|
393
464
|
|
|
394
|
-
##
|
|
465
|
+
## stateMutator
|
|
395
466
|
|
|
396
|
-
Similarly, the **
|
|
467
|
+
Similarly, the **contactsMutator** method allows you to modify the state stored in **useContacts**. You can use this method to update the state with a new value or perform any necessary state mutations without the restrictions imposed by **hooks**.
|
|
397
468
|
|
|
398
469
|
These additional methods provide a more flexible and granular way to interact with the state managed by **useContacts**. You can retrieve and modify the state as needed, without establishing a subscription relationship or reactivity with the state changes.
|
|
399
470
|
|
|
@@ -433,32 +504,6 @@ export const useCount = createGlobalState(0, {
|
|
|
433
504
|
|
|
434
505
|
Notice that the **StoreTools** will contain a reference to the generated actions API. From there, you'll be able to access all actions from inside another one... the **StoreTools** is generic and allow your to set an interface for getting the typing on the actions.
|
|
435
506
|
|
|
436
|
-
If you don't want to create an extra type please use **createGlobalStateWithDecoupledFuncs** in that way you'll be able to use the decoupled **actions** which will have the correct typing. Let's take a quick look into that:
|
|
437
|
-
|
|
438
|
-
```ts
|
|
439
|
-
import { createGlobalStateWithDecoupledFuncs } from 'react-hooks-global-states';
|
|
440
|
-
|
|
441
|
-
export const [useCount, getCount, $actions] = createGlobalStateWithDecoupledFuncs(0, {
|
|
442
|
-
actions: {
|
|
443
|
-
log: (currentValue: string) => {
|
|
444
|
-
return ({ getState }: StoreTools<number>): void => {
|
|
445
|
-
console.log(`Current Value: ${getState()}`);
|
|
446
|
-
};
|
|
447
|
-
},
|
|
448
|
-
|
|
449
|
-
increase(value: number = 1) {
|
|
450
|
-
return ({ getState, setState }: StoreTools<number>) => {
|
|
451
|
-
setState((count) => count + value);
|
|
452
|
-
|
|
453
|
-
$actions.log(message);
|
|
454
|
-
};
|
|
455
|
-
},
|
|
456
|
-
} as const,
|
|
457
|
-
});
|
|
458
|
-
```
|
|
459
|
-
|
|
460
|
-
In the example the hook will work the same and you'll have access to the correct typing.
|
|
461
|
-
|
|
462
507
|
# Stateful Context with Actions
|
|
463
508
|
|
|
464
509
|
**The ultimate blend of flexibility and control in React state management!** You can now create an isolated global state within a React context, giving each consumer of the context provider a unique state instance. But that’s not all...
|
|
@@ -500,7 +545,7 @@ What’s the advantage of this, you might ask? Well, now you have all the capabi
|
|
|
500
545
|
const MyComponent = () => {
|
|
501
546
|
const [, , setCount] = useCounterContext();
|
|
502
547
|
|
|
503
|
-
// This component can access only the
|
|
548
|
+
// This component can access only the stateMutator of the state,
|
|
504
549
|
// and won't re-render if the counter changes
|
|
505
550
|
return <button onClick={() => setCount((count) => count + 1)}>Increase</button>;
|
|
506
551
|
};
|
|
@@ -560,7 +605,7 @@ export const [useCounterContext, CounterProvider] = createStatefulContext(initia
|
|
|
560
605
|
});
|
|
561
606
|
```
|
|
562
607
|
|
|
563
|
-
And just like with regular global hooks, now instead of a
|
|
608
|
+
And just like with regular global hooks, now instead of a setState function, the hook will return the collection of actions:
|
|
564
609
|
|
|
565
610
|
```tsx
|
|
566
611
|
const MyComponent = () => {
|
|
@@ -756,12 +801,12 @@ export class GlobalStore<
|
|
|
756
801
|
asyncStorageKey?: string;
|
|
757
802
|
isAsyncStorageReady?: boolean;
|
|
758
803
|
} | null = null,
|
|
759
|
-
|
|
760
|
-
> extends GlobalStoreAbstract<TState, TMetadata,
|
|
804
|
+
TStateMutator extends ActionCollectionConfig<TState, TMetadata> | StateSetter<TState> = StateSetter<TState>
|
|
805
|
+
> extends GlobalStoreAbstract<TState, TMetadata, TStateMutator> {
|
|
761
806
|
constructor(
|
|
762
807
|
state: TState,
|
|
763
|
-
config: GlobalStoreConfig<TState, TMetadata,
|
|
764
|
-
actionsConfig:
|
|
808
|
+
config: GlobalStoreConfig<TState, TMetadata, TStateMutator> = {},
|
|
809
|
+
actionsConfig: TStateMutator | null = null
|
|
765
810
|
) {
|
|
766
811
|
super(state, config, actionsConfig);
|
|
767
812
|
|
|
@@ -773,7 +818,7 @@ export class GlobalStore<
|
|
|
773
818
|
setMetadata,
|
|
774
819
|
getMetadata,
|
|
775
820
|
getState,
|
|
776
|
-
}: StateConfigCallbackParam<TState, TMetadata,
|
|
821
|
+
}: StateConfigCallbackParam<TState, TMetadata, TStateMutator>) => {
|
|
777
822
|
setMetadata({
|
|
778
823
|
...(metadata ?? {}),
|
|
779
824
|
isAsyncStorageReady: null,
|
|
@@ -807,7 +852,7 @@ export class GlobalStore<
|
|
|
807
852
|
protected onChange = ({
|
|
808
853
|
getMetadata,
|
|
809
854
|
getState,
|
|
810
|
-
}: StateChangesParam<TState, TMetadata, NonNullable<
|
|
855
|
+
}: StateChangesParam<TState, TMetadata, NonNullable<TStateMutator>>) => {
|
|
811
856
|
const asyncStorageKey = getMetadata()?.asyncStorageKey;
|
|
812
857
|
|
|
813
858
|
if (!asyncStorageKey) return;
|