react-hooks-global-states 1.0.6 → 1.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/README.md CHANGED
@@ -61,9 +61,7 @@ Now, let's say we want to have a filter bar for the contacts that will only have
61
61
  ```ts
62
62
  const [{ filter }, setState] = useContacts(({ filter }) => ({ filter }));
63
63
 
64
- return (
65
- <TextInput onChangeText={() => setState((state) => ({ ...state, filter }))} />
66
- );
64
+ return <TextInput onChangeText={() => setState((state) => ({ ...state, filter }))} />;
67
65
  ```
68
66
 
69
67
  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.
@@ -87,12 +85,10 @@ Well, that's it! Now you can simply call **useFilter** inside your component, an
87
85
  ```ts
88
86
  const [{ filter }, setState] = useFilter();
89
87
 
90
- return (
91
- <TextInput onChangeText={() => setState((state) => ({ ...state, filter }))} />
92
- );
88
+ return <TextInput onChangeText={() => setState((state) => ({ ...state, filter }))} />;
93
89
  ```
94
90
 
95
- Notice that the **state** changes, but the **setter** 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.
91
+ 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.
96
92
 
97
93
  # State actions
98
94
 
@@ -129,7 +125,7 @@ export const useContacts = createGlobalState(initialState, {
129
125
  });
130
126
  ```
131
127
 
132
- That's it! In this updated version, the **useContacts** hook will no longer return [**state**, **stateSetter**] but instead will return [**state**, **actions**]. This change will provide a more intuitive and convenient way to access and interact with the state and its associated actions.
128
+ 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.
133
129
 
134
130
  Let's see how that will look now into our **FilterBar.tsx**
135
131
 
@@ -159,42 +155,52 @@ It can't get any simpler, right? Everything is connected, everything is reactive
159
155
 
160
156
  # Decoupled state access
161
157
 
162
- 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 the **createGlobalStateWithDecoupledFuncs**.
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:
159
+
160
+ ```tsx
161
+ GlobalStateHook.stateControls: () => [stateRetriever: StateGetter<State>, stateMutator: Setter<State>|ActionCollectionResult<State>];
162
+
163
+ // example:
164
+ const [getContacts, setContacts] = useContacts.stateControls();
163
165
 
164
- Decoupled state access 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.
166
+ console.log(getContacts()); // prints the list of contacts
167
+ ```
165
168
 
166
- Using decoupled state access 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:
169
+ **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.
170
+
171
+ 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:
167
172
 
168
173
  ```ts
169
- import { createGlobalStateWithDecoupledFuncs } from 'react-hooks-global-states';
174
+ import { createGlobalState } from 'react-hooks-global-states';
170
175
 
171
- export const [useContacts, contactsGetter, contactsSetter] =
172
- createGlobalStateWithDecoupledFuncs({
173
- isLoading: true,
174
- filter: '',
175
- items: [] as Contact[],
176
- });
176
+ export const useContacts = createGlobalState({
177
+ isLoading: true,
178
+ filter: '',
179
+ items: [] as Contact[],
180
+ });
181
+
182
+ export const [contactsRetriever, contactsMutator] = useContacts.stateControls();
177
183
  ```
178
184
 
179
- That's great! With the addition of the **contactsGetter** and **contactsSetter** methods, you now have the ability to access and modify the state without the need for subscription to the hook.
185
+ 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.
180
186
 
181
- While **useContacts** will allow your components to subscribe to the custom hook, using the **contactsGetter** 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:
187
+ 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:
182
188
 
183
189
  ```ts
184
190
  // To synchronously get the value of the state
185
- const value = contactsGetter();
191
+ const value = contactsRetriever();
186
192
 
187
193
  // the type of value will be { isLoading: boolean; filter: string; items: Contact[] }
188
194
  ```
189
195
 
190
- Additionally, to subscribe to state changes, you can pass a callback function as a parameter to the **getter**. 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 **getter**, 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.
196
+ 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.
191
197
 
192
198
  ```ts
193
199
  /**
194
200
  * This not only allows you to retrieve the current value of the state...
195
201
  * but also enables you to subscribe to any changes in the state or a portion of it
196
202
  */
197
- const removeSubscriptionGroup = contactsGetter<Subscribe>((subscribe) => {
203
+ const removeSubscriptionGroup = contactsRetriever<Subscribe>((subscribe) => {
198
204
  subscribe((state) => {
199
205
  console.log('state changed: ', state);
200
206
  });
@@ -215,15 +221,12 @@ That's great, isn't it? everything stays synchronized with the original state!!
215
221
  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:
216
222
 
217
223
  ```ts
218
- const subscribeToFilter = createDerivateEmitter(
219
- contactsGetter,
220
- ({ filter }) => ({
221
- filter,
222
- })
223
- );
224
+ const subscribeToFilter = createDerivateEmitter(contactsRetriever, ({ filter }) => ({
225
+ filter,
226
+ }));
224
227
  ```
225
228
 
226
- Cool, it's basically the same, but instead of using the **hook** as a parameter, we just have to use the **getter** as a parameter, and that will make the magic.
229
+ 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.
227
230
 
228
231
  Now we are able to add a callback that will be executed every time the state of the **filter** changes.
229
232
 
@@ -272,61 +275,59 @@ const removeFilterSubscription = subscribeToFilter<Subscribe>(
272
275
  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:
273
276
 
274
277
  ```ts
275
- const subscribeToItems = createDerivateEmitter(
276
- contactsGetter,
277
- ({ items }) => items
278
- );
278
+ const subscribeToItems = createDerivateEmitter(contactsRetriever, ({ items }) => items);
279
279
 
280
- const subscribeToItemsLength = createDerivateEmitter(
281
- subscribeToItems,
282
- (items) => items.length
283
- );
280
+ const subscribeToItemsLength = createDerivateEmitter(subscribeToItems, (items) => items.length);
284
281
  ```
285
282
 
286
283
  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!
287
284
 
288
- # Combining getters
285
+ # Combining stateRetriever
289
286
 
290
- 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 **getters**.
287
+ 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**.
291
288
 
292
289
  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:
293
290
 
294
- Fist we are gonna create a couple of **global state**, is important to create them with the **createGlobalStateWithDecoupledFuncs** since we need the decoupled **getter**. (In case you are using an instance of **GlobalStore** or **GlobalStoreAbstract** you can just pick up the getters from the **getHookDecoupled** method)
291
+ Fist 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)
295
292
 
296
293
  ```ts
297
- const [useHook1, getter1, setter1] = createGlobalStateWithDecoupledFuncs({
294
+ const useHook1 = createGlobalState({
298
295
  propA: 1,
299
296
  propB: 2,
300
297
  });
301
298
 
302
- const [, getter2] = createGlobalStateWithDecoupledFuncs({
299
+ const [stateRetriever1, stateMutator1] = useHook1.stateControls();
300
+
301
+ const useHook2 = createGlobalState({
303
302
  propC: 3,
304
303
  propD: 4,
305
304
  });
305
+
306
+ const [, stateRetriever2] = useHook2.stateControls();
306
307
  ```
307
308
 
308
309
  Okay, cool, the first state as **propA, propB** while the second one has **propC, propD**, let's combine them:
309
310
 
310
311
  ```ts
311
- const [useCombinedHook, getter, dispose] = combineAsyncGetters(
312
+ const [useCombinedHook, stateRetriever, dispose] = combineAsyncGetters(
312
313
  {
313
314
  selector: ([state1, state2]) => ({
314
315
  ...state1,
315
316
  ...state2,
316
317
  }),
317
318
  },
318
- getter1,
319
- getter2
319
+ stateRetriever1,
320
+ stateRetriever2
320
321
  );
321
322
  ```
322
323
 
323
- Well, that's it! Now you have access to a **getter** that will return the combined value of the two states. From this new **getter**, you can retrieve the value or subscribe to its changes. Let'see:
324
+ 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:
324
325
 
325
326
  ```ts
326
- const value = getter(); // { propA, propB, propC, propD }
327
+ const value = stateRetriever(); // { propA, propB, propC, propD }
327
328
 
328
329
  // subscribe to the new emitter
329
- const unsubscribeGroup = getter<Subscribe>((subscribe) => {
330
+ const unsubscribeGroup = stateRetriever<Subscribe>((subscribe) => {
330
331
  subscribe((state) => {
331
332
  console.log(subscribe); // full state
332
333
  });
@@ -358,35 +359,38 @@ Similar to your other **global state hooks**, **combined hooks** allow you to us
358
359
  const [fragment] = useCombinedHook(({ propA, propD }) => ({ propA, propD }));
359
360
  ```
360
361
 
361
- Lastly, you have the flexibility to continue combining getters if desired. This means you can extend the functionality of combined hooks by adding more getters to merge additional states. By combining getters 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.
362
+ 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.
362
363
 
363
364
  Let's see an example:
364
365
 
365
366
  ```ts
366
- const [useCombinedHook, combinedGetter1, dispose1] = combineAsyncGetters(
367
+ const [useCombinedHook, combinedStateRetriever1, dispose1] = combineAsyncGetters(
367
368
  {
368
369
  selector: ([state1, state2]) => ({
369
370
  ...state1,
370
371
  ...state2,
371
372
  }),
372
373
  },
373
- getter1,
374
- getter2
374
+ stateRetriever1,
375
+ stateRetriever2
375
376
  );
376
377
 
377
- const [useHook3, getter3, setter3] = createGlobalStateWithDecoupledFuncs({
378
+ const useHook3 = createGlobalState({
378
379
  propE: 1,
379
380
  propF: 2,
380
381
  });
381
382
 
382
- const [useIsLoading, isLoadingGetter, isLoadingSetter] =
383
- createGlobalStateWithDecoupledFuncs(false);
383
+ const [stateRetriever3, stateMutator3] = useHook3.stateControls();
384
+
385
+ const useIsLoading = createGlobalState(false);
386
+
387
+ const [isLoadingStateRetriever, isLoadingMutator] = useIsLoading.stateControls();
384
388
  ```
385
389
 
386
390
  Once we created another peace of state, we can combine it with our other **global hooks** and **emitters**
387
391
 
388
392
  ```ts
389
- const [useCombinedHook2, combinedGetter2, dispose2] = combineAsyncGetters(
393
+ const [useCombinedHook2, combinedStateRetriever2, dispose2] = combineAsyncGetters(
390
394
  {
391
395
  selector: ([state1, state2, isLoading]) => ({
392
396
  ...state1,
@@ -394,9 +398,9 @@ const [useCombinedHook2, combinedGetter2, dispose2] = combineAsyncGetters(
394
398
  isLoading,
395
399
  }),
396
400
  },
397
- combinedGetter1,
398
- getter3,
399
- isLoadingGetter
401
+ combinedStateRetriever1,
402
+ stateRetriever3,
403
+ isLoadingStateRetriever
400
404
  );
401
405
  ```
402
406
 
@@ -406,9 +410,9 @@ You have the freedom to combine as many global hooks as you wish. This means you
406
410
 
407
411
  Please be aware that the third parameter is a **dispose callback**, which can be particularly useful in **high-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.
408
412
 
409
- ## Setter
413
+ ## stateMutator
410
414
 
411
- Similarly, the **contactsSetter** 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**.
415
+ 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**.
412
416
 
413
417
  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.
414
418
 
@@ -448,32 +452,116 @@ export const useCount = createGlobalState(0, {
448
452
 
449
453
  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.
450
454
 
451
- 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:
455
+ # Stateful Context with Actions
452
456
 
453
- ```ts
454
- import { createGlobalStateWithDecoupledFuncs } from 'react-hooks-global-states';
455
-
456
- export const [useCount, getCount, $actions] =
457
- createGlobalStateWithDecoupledFuncs(0, {
458
- actions: {
459
- log: (currentValue: string) => {
460
- return ({ getState }: StoreTools<number>): void => {
461
- console.log(`Current Value: ${getState()}`);
462
- };
463
- },
464
-
465
- increase(value: number = 1) {
466
- return ({ getState, setState }: StoreTools<number>) => {
467
- setState((count) => count + value);
468
-
469
- $actions.log(message);
470
- };
471
- },
472
- } as const,
473
- });
457
+ **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...
458
+
459
+ **Stateful Context with Actions** extends the powerful features of global hooks into the realm of React Context. By integrating global hooks within a context, you bring all the benefits of global state management—such as modularity, selectors, derived states, and actions—into a context-specific environment. This means each consumer of the context not only gets a unique state instance but also inherits all the advanced capabilities of global hooks.
460
+
461
+ ## Creating a Stateful Context
462
+
463
+ Forget about the boilerplate of creating a context... with **createStatefulContext** it's straightforward and powerful. You can create a context and provider with one line of code.
464
+
465
+ ```tsx
466
+ export const [useCounterContext, CounterProvider] = createStatefulContext(2);
467
+ ```
468
+
469
+ Then just wrap the components you need with the provider:
470
+
471
+ ```tsx
472
+ <CounterProvider>
473
+ <MyComponent />
474
+ </CounterProvider>
475
+ ```
476
+
477
+ And finally, access the context value with the generated custom hook:
478
+
479
+ ```tsx
480
+ const MyComponent = () => {
481
+ const [useCounter] = useCounterContext();
482
+
483
+ // If the component needs to react to state changes, simply use the hook
484
+ const [count, setCount] = useCounter();
485
+
486
+ return <>{count}</>;
487
+ };
474
488
  ```
475
489
 
476
- In the example the hook will work the same and you'll have access to the correct typing.
490
+ What’s the advantage of this, you might ask? Well, now you have all the capabilities of the global hooks within the isolated scope of the context. For example, you can choose whether or not to listen to changes in the state:
491
+
492
+ ```tsx
493
+ const MyComponent = () => {
494
+ const [, , setCount] = useCounterContext();
495
+
496
+ // This component can access only the stateMutator of the state,
497
+ // and won't re-render if the counter changes
498
+ return <button onClick={() => setCount((count) => count + 1)}>Increase</button>;
499
+ };
500
+ ```
501
+
502
+ Now you have selectors—if the state changes, the component will only re-render if the selected portion of the state changes.
503
+
504
+ ```tsx
505
+ const MyComponent = () => {
506
+ const [useCounter] = useCounterContext();
507
+
508
+ // Notice that we can select and derive values from the state
509
+ const [isEven, setCount] = useCounter((count) => count % 2 === 0);
510
+
511
+ useEffect(() => {
512
+ // Since the counter initially was 2 and now is 4, it’s still an even number.
513
+ // Because of this, the component will not re-render.
514
+ setCount(4);
515
+ }, []);
516
+
517
+ return <>{isEven ? 'is even' : 'is odd'}</>;
518
+ };
519
+ ```
520
+
521
+ **createStatefulContext** also allows you to add custom actions to control the manipulation of the state.
522
+
523
+ ```tsx
524
+ import { createStatefulContext, StoreTools } from 'react-global-state-hooks';
525
+
526
+ type CounterState = {
527
+ count: number;
528
+ };
529
+
530
+ const initialState: CounterState = {
531
+ count: 0,
532
+ };
533
+
534
+ export const [useCounterContext, CounterProvider] = createStatefulContext(initialState, {
535
+ actions: {
536
+ increase: (value: number = 1) => {
537
+ return ({ setState }: StoreTools<CounterState>) => {
538
+ setState((state) => ({
539
+ ...state,
540
+ count: state.count + value,
541
+ }));
542
+ };
543
+ },
544
+ decrease: (value: number = 1) => {
545
+ return ({ setState }: StoreTools<CounterState>) => {
546
+ setState((state) => ({
547
+ ...state,
548
+ count: state.count - value,
549
+ }));
550
+ };
551
+ },
552
+ } as const,
553
+ });
554
+ ```
555
+
556
+ And just like with regular global hooks, now instead of a setState function, the hook will return the collection of actions:
557
+
558
+ ```tsx
559
+ const MyComponent = () => {
560
+ const [, , actions] = useCounterContext();
561
+
562
+ return <button onClick={() => actions.increase(1)}>Increase</button>;
563
+ };
564
+ ```
477
565
 
478
566
  # Extending Global Hooks
479
567
 
@@ -661,14 +749,12 @@ export class GlobalStore<
661
749
  asyncStorageKey?: string;
662
750
  isAsyncStorageReady?: boolean;
663
751
  } | null = null,
664
- TStateSetter extends
665
- | ActionCollectionConfig<TState, TMetadata>
666
- | StateSetter<TState> = StateSetter<TState>
667
- > extends GlobalStoreAbstract<TState, TMetadata, TStateSetter> {
752
+ TStateMutator extends ActionCollectionConfig<TState, TMetadata> | StateSetter<TState> = StateSetter<TState>
753
+ > extends GlobalStoreAbstract<TState, TMetadata, TStateMutator> {
668
754
  constructor(
669
755
  state: TState,
670
- config: GlobalStoreConfig<TState, TMetadata, TStateSetter> = {},
671
- actionsConfig: TStateSetter | null = null
756
+ config: GlobalStoreConfig<TState, TMetadata, TStateMutator> = {},
757
+ actionsConfig: TStateMutator | null = null
672
758
  ) {
673
759
  super(state, config, actionsConfig);
674
760
 
@@ -680,7 +766,7 @@ export class GlobalStore<
680
766
  setMetadata,
681
767
  getMetadata,
682
768
  getState,
683
- }: StateConfigCallbackParam<TState, TMetadata, TStateSetter>) => {
769
+ }: StateConfigCallbackParam<TState, TMetadata, TStateMutator>) => {
684
770
  setMetadata({
685
771
  ...(metadata ?? {}),
686
772
  isAsyncStorageReady: null,
@@ -714,7 +800,7 @@ export class GlobalStore<
714
800
  protected onChange = ({
715
801
  getMetadata,
716
802
  getState,
717
- }: StateChangesParam<TState, TMetadata, NonNullable<TStateSetter>>) => {
803
+ }: StateChangesParam<TState, TMetadata, NonNullable<TStateMutator>>) => {
718
804
  const asyncStorageKey = getMetadata()?.asyncStorageKey;
719
805
 
720
806
  if (!asyncStorageKey) return;