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 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
- ```ts
62
- const [{ filter }, setState] = useContacts(({ filter }) => ({ filter }));
63
-
64
- return <TextInput onChangeText={() => setState((state) => ({ ...state, filter }))} />;
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
- By the way, in the example, the **selector** returning a new object is not a problem at all. This is because, by default, there is a shallow comparison between the previous and current versions of the state, so the render won't trigger if it's not necessary.
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 **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.
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**, **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.
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 the **createGlobalStateWithDecoupledFuncs**.
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
- 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.
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 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:
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 { createGlobalStateWithDecoupledFuncs } from 'react-hooks-global-states';
226
+ import { createGlobalState } from 'react-hooks-global-states';
166
227
 
167
- export const [useContacts, contactsGetter, contactsSetter] = createGlobalStateWithDecoupledFuncs({
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 **contactsGetter** and **contactsSetter** methods, you now have the ability to access and modify the state without the need for subscription to the hook.
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 **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:
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 = contactsGetter();
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 **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.
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 = contactsGetter<Subscribe>((subscribe) => {
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(contactsGetter, ({ filter }) => ({
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 **getter** as a parameter, and that will make the magic.
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(contactsGetter, ({ items }) => items);
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 getters
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 **getters**.
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
- 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)
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 [useHook1, getter1, setter1] = createGlobalStateWithDecoupledFuncs({
346
+ const useHook1 = createGlobalState({
284
347
  propA: 1,
285
348
  propB: 2,
286
349
  });
287
350
 
288
- const [, getter2] = createGlobalStateWithDecoupledFuncs({
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, getter, dispose] = combineAsyncGetters(
364
+ const [useCombinedHook, stateRetriever, dispose] = combineAsyncGetters(
298
365
  {
299
366
  selector: ([state1, state2]) => ({
300
367
  ...state1,
301
368
  ...state2,
302
369
  }),
303
370
  },
304
- getter1,
305
- getter2
371
+ stateRetriever1,
372
+ stateRetriever2
306
373
  );
307
374
  ```
308
375
 
309
- 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:
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 = getter(); // { propA, propB, propC, propD }
379
+ const value = stateRetriever(); // { propA, propB, propC, propD }
313
380
 
314
381
  // subscribe to the new emitter
315
- const unsubscribeGroup = getter<Subscribe>((subscribe) => {
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 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.
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, combinedGetter1, dispose1] = combineAsyncGetters(
419
+ const [useCombinedHook, combinedStateRetriever1, dispose1] = combineAsyncGetters(
353
420
  {
354
421
  selector: ([state1, state2]) => ({
355
422
  ...state1,
356
423
  ...state2,
357
424
  }),
358
425
  },
359
- getter1,
360
- getter2
426
+ stateRetriever1,
427
+ stateRetriever2
361
428
  );
362
429
 
363
- const [useHook3, getter3, setter3] = createGlobalStateWithDecoupledFuncs({
430
+ const useHook3 = createGlobalState({
364
431
  propE: 1,
365
432
  propF: 2,
366
433
  });
367
434
 
368
- const [useIsLoading, isLoadingGetter, isLoadingSetter] = createGlobalStateWithDecoupledFuncs(false);
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, combinedGetter2, dispose2] = combineAsyncGetters(
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
- combinedGetter1,
383
- getter3,
384
- isLoadingGetter
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 **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.
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
- ## Setter
465
+ ## stateMutator
395
466
 
396
- 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**.
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 setter of the state,
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 setter, the hook will return the collection of actions:
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
- TStateSetter extends ActionCollectionConfig<TState, TMetadata> | StateSetter<TState> = StateSetter<TState>
760
- > extends GlobalStoreAbstract<TState, TMetadata, TStateSetter> {
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, TStateSetter> = {},
764
- actionsConfig: TStateSetter | null = null
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, TStateSetter>) => {
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<TStateSetter>>) => {
855
+ }: StateChangesParam<TState, TMetadata, NonNullable<TStateMutator>>) => {
811
856
  const asyncStorageKey = getMetadata()?.asyncStorageKey;
812
857
 
813
858
  if (!asyncStorageKey) return;