react-native-global-state-hooks 7.0.2 → 8.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.
Files changed (50) hide show
  1. package/GlobalStore.d.ts +29 -0
  2. package/GlobalStore.js +1 -0
  3. package/GlobalStoreAbstract.d.ts +9 -0
  4. package/GlobalStoreAbstract.js +1 -0
  5. package/README.md +340 -334
  6. package/asyncStorageWrapper.d.ts +8 -0
  7. package/asyncStorageWrapper.js +1 -0
  8. package/bundle.js +1 -0
  9. package/combineRetrieverAsynchronously.d.ts +1 -0
  10. package/combineRetrieverAsynchronously.js +1 -0
  11. package/combineRetrieverEmitterAsynchronously.d.ts +1 -0
  12. package/combineRetrieverEmitterAsynchronously.js +1 -0
  13. package/createContext.d.ts +1 -0
  14. package/createContext.js +1 -0
  15. package/createCustomGlobalState.d.ts +22 -0
  16. package/createCustomGlobalState.js +1 -0
  17. package/createGlobalState.d.ts +20 -0
  18. package/createGlobalState.js +1 -0
  19. package/debounce.d.ts +1 -0
  20. package/debounce.js +1 -0
  21. package/getAsyncStorageItem.d.ts +2 -0
  22. package/getAsyncStorageItem.js +1 -0
  23. package/index.d.ts +23 -0
  24. package/isRecord.d.ts +1 -0
  25. package/isRecord.js +1 -0
  26. package/package.json +94 -8
  27. package/setAsyncStorageItem.d.ts +2 -0
  28. package/setAsyncStorageItem.js +1 -0
  29. package/shallowCompare.d.ts +1 -0
  30. package/shallowCompare.js +1 -0
  31. package/throwWrongKeyOnActionCollectionConfig.d.ts +1 -0
  32. package/throwWrongKeyOnActionCollectionConfig.js +1 -0
  33. package/types.d.ts +83 -0
  34. package/types.js +1 -0
  35. package/uniqueId.d.ts +1 -0
  36. package/uniqueId.js +1 -0
  37. package/uniqueSymbol.d.ts +1 -0
  38. package/uniqueSymbol.js +1 -0
  39. package/useConstantValueRef.d.ts +1 -0
  40. package/useConstantValueRef.js +1 -0
  41. package/webpack.config.js +89 -0
  42. package/lib/bundle.js +0 -2
  43. package/lib/bundle.js.LICENSE.txt +0 -1
  44. package/lib/src/GlobalStore.context.d.ts +0 -6
  45. package/lib/src/GlobalStore.d.ts +0 -28
  46. package/lib/src/GlobalStore.functionHooks.d.ts +0 -19
  47. package/lib/src/GlobalStore.types.d.ts +0 -124
  48. package/lib/src/GlobalStore.utils.d.ts +0 -16
  49. package/lib/src/GlobalStoreAbstract.d.ts +0 -16
  50. package/lib/src/index.d.ts +0 -12
package/README.md CHANGED
@@ -23,7 +23,7 @@ The best part? [react-hooks-global-states](https://www.npmjs.com/package/react-h
23
23
  We are gonna create a global state hook **useCount** with one line of code.
24
24
 
25
25
  ```ts
26
- import { createGlobalState } from "react-native-global-state-hooks";
26
+ import { createGlobalState } from "react-native-global-state-hooks/createGlobalState";
27
27
 
28
28
  export const useCount = createGlobalState(0);
29
29
  ```
@@ -68,76 +68,154 @@ The metadata and components will be updated and re-rendered even if there's no d
68
68
  What if you already have a global state that you want to subscribe to, but you don't want your component to listen to all the changes of the state, only a small portion of it? Let's create a more complex **state**
69
69
 
70
70
  ```ts
71
- import { createGlobalState } from "react-native-global-state-hooks";
71
+ import { createGlobalState } from 'react-hooks-global-states';
72
72
 
73
73
  export const useContacts = createGlobalState({
74
74
  isLoading: true,
75
- filter: "",
76
- items: [] as Contact[],
75
+ entities: Contact[],
76
+ selected: Set<number>,
77
77
  });
78
78
  ```
79
79
 
80
- Now, let's say we want to have a filter bar for the contacts that will only have access to the filter.
81
-
82
- **FilterBar.tsx**
80
+ Now, let's say we have a situation where we want to access only the list of contacts. We don't care about the rest of the state.
83
81
 
84
82
  ```tsx
85
- const [contacts] = useContacts((state) =>
86
- state.contacts.filter((contact) => contact.status === "active")
87
- );
83
+ // That's it. With that simple selector, we now get the list of contacts,
84
+ // and the component will only re-render if the property **entities** changes on the global state
85
+ const [contacts] = useContacts((state) => state.entities]);
88
86
 
89
87
  return (
90
- <View>
88
+ <ul>
91
89
  {contacts.map((contact) => (
92
- <Text key={contact.id}>{contact.name}</Text>
90
+ <li key={contact.id}>{contact.name}</li>
93
91
  ))}
94
- </View>
92
+ </ul>
95
93
  );
96
94
  ```
97
95
 
98
- 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.
96
+ What about special cases, like when you have a map instead of an array and want to extract a list of contacts? It's common to use selectors that return a new array, but this can cause React to re-render because the new array has a different reference than the previous one.
97
+
98
+ ```tsx
99
+ export const useContacts = createGlobalState({
100
+ isLoading: true,
101
+ entities: Map<number, Contact>,
102
+ selected: Set<number>,
103
+ });
104
+
105
+ // The selector is simply a standard selector used to extract the values from the map.
106
+
107
+ const [contacts] = useContacts((state) => [...state.entities.values()], {
108
+ // The isEqualRoots function allows you to create your own validation logic for determining when to recompute the selector.
109
+ isEqualRoot: (a, b) => a.entities === b.entities,
110
+ });
111
+ ```
112
+
113
+ Okay, everything works when the changes come from the state, but what happens if I want to recompute the selector based on the internal state of the component?
99
114
 
100
- If you want to have more control over when the hook should recompute the selector result, there are a couple of options:
115
+ **component.ts**
101
116
 
102
117
  ```tsx
103
118
  const [filter, setFilter] = useState("");
104
119
 
105
120
  const [contacts] = useContacts(
106
- (state) => state.contacts.filter((contact) => contact.name.includes(filter)),
121
+ (state) => [...state.entities.values()].filter((item) => item.name.includes(filter)),
107
122
  {
123
+ isEqualRoot: (a, b) => a.entities === b.entities,
108
124
  /**
109
- * You can use the `isEqualRoot` to validate if the values before the selector are equal.
110
- * This validation will run before `isEqual` and if the result is true the selector will not be recomputed.
111
- * If the result is true the re-render of the component will be prevented.
112
- */
113
- isEqualRoot: (r1, r2) => r1.filter === r2.filter,
114
-
115
- /**
116
- * You can use the `isEqual` to validate if the values after the selector are equal.
117
- * This validation will run after the selector computed a new value...
118
- * and if the result is true it will prevent the re-render of the component.
119
- */
120
- isEqual: (filter1, filter2) => filter1 === filter2,
121
-
122
- /**
123
- * You can use the `dependencies` array as with regular hooks to to force the recomputation of the selector.
124
- * Is important ot mention that changes in the dependencies will not trigger a re-render of the component...
125
- * Instead the recomputation of the selector will returned immediately.
126
- */
125
+ * Easy to understand, right? With the dependencies prop, you can,
126
+ * just like with any other hook, provide a collection of values that will be compared during each render cycle
127
+ * to determine if the selector should be recomputed.*/
127
128
  dependencies: [filter],
128
129
  }
129
130
  );
131
+ ```
132
+
133
+ And finally, what if you need to reuse this selector throughout your application and don't want to duplicate code?
134
+
135
+ ```tsx
136
+ export const useContacts = createGlobalState({
137
+ isLoading: true,
138
+ entities: Map<number, Contact>,
139
+ selected: Set<number>,
140
+ });
141
+
142
+ const useContactsArray = useContacts.createSelectorHook((state) => [...state.entities.values()], {
143
+ isEqualRoot: (a, b) => a.entities === b.entities,
144
+ });
145
+ ```
146
+
147
+ Now inside your component just call the new hook
148
+
149
+ **component.ts**
150
+
151
+ ```tsx
152
+ const [filter, setFilter] = useState("");
153
+
154
+ const [contacts] = useContactsArray((entities) => entities.name.includes(filter), {
155
+ dependencies: [filter],
156
+ });
157
+ ```
158
+
159
+ Or you can create another selectorHook from your **useContactsArray**
160
+
161
+ ```ts
162
+ const useContactsArray = useContacts.createSelectorHook((state) => [...state.entities.values()], {
163
+ isEqualRoot: (a, b) => a.entities === b.entities,
164
+ });
165
+
166
+ const useContactsLength = useContactsArray.createSelectorHook((entities) => entities.length);
167
+ ```
168
+
169
+ Or you can create a custom hook
170
+
171
+ ```tsx
172
+ const useFilteredContacts = (filter: string) => {
173
+ const [contacts] = useContactsArray((entities) => entities.name.includes(filter), {
174
+ dependencies: [filter],
175
+ });
176
+
177
+ return contacts;
178
+ };
179
+ ```
180
+
181
+ To summarize
182
+
183
+ ```tsx
184
+ const [filter, setFilter] = useState("");
185
+
186
+ const [contacts] = useContacts((state) => state.contacts.filter((contact) => contact.name.includes(filter)), {
187
+ /**
188
+ * You can use the `isEqualRoot` to validate if the values before the selector are equal.
189
+ * This validation will run before `isEqual` and if the result is true the selector will not be recomputed.
190
+ * If the result is true the re-render of the component will be prevented.
191
+ */
192
+ isEqualRoot: (r1, r2) => r1.filter === r2.filter,
193
+
194
+ /**
195
+ * You can use the `isEqual` to validate if the values after the selector are equal.
196
+ * This validation will run after the selector computed a new value...
197
+ * and if the result is true it will prevent the re-render of the component.
198
+ */
199
+ isEqual: (filter1, filter2) => filter1 === filter2,
200
+
201
+ /**
202
+ * You can use the `dependencies` array as with regular hooks to to force the recomputation of the selector.
203
+ * Is important ot mention that changes in the dependencies will not trigger a re-render of the component...
204
+ * Instead the recomputation of the selector will returned immediately.
205
+ */
206
+ dependencies: [filter],
207
+ });
130
208
 
131
209
  return (
132
- <View>
210
+ <ul>
133
211
  {contacts.map((contact) => (
134
- <Text key={contact.id}>{contact.name}</Text>
212
+ <li key={contact.id}>{contact.name}</li>
135
213
  ))}
136
- </View>
214
+ </ul>
137
215
  );
138
216
  ```
139
217
 
140
- If you want to perform a shallow comparison between the previous and new values, you can use the **shallowCompare** function from the library.
218
+ Btw, If you want to perform a shallow comparison between the previous and new values, you can use the **shallowCompare** function from the library.
141
219
 
142
220
  ```TSX
143
221
  ({
@@ -148,29 +226,23 @@ If you want to perform a shallow comparison between the previous and new values,
148
226
  })
149
227
  ```
150
228
 
151
- ## What if you want to reuse the selector?
152
-
153
- It will be super common to have the necessity of reusing a specific **selector**, and it can be a little annoying to have to do the same thing again and again. Right?
229
+ Just remember, you can select or derive different values from the global state endlessly, but the state mutator will remain the same throughout the hooks.
154
230
 
155
- No problem, you can create a reusable **derivative-state** and use it across your components. Let's create one for our filter.
231
+ More examples:
156
232
 
157
233
  ```ts
158
- const useFilter = createDerivate(useContacts, ({ filter }) => ({ filter }));
159
- ```
234
+ const useFilter = useContacts.createSelectorHook(({ filter }) => filter);
160
235
 
161
- Well, that's it! Now you can simply call **useFilter** inside your component, and everything will continue to work the same.
236
+ const useContactsArray = useContacts.createSelectorHook(({ items }) => items);
162
237
 
163
- **FilterBar.tsx**
164
-
165
- ```ts
166
- const [{ filter }, setState] = useFilter();
238
+ const useContactsLength = useContactsArray.createSelectorHook((items) => items.length);
167
239
 
168
- return (
169
- <TextInput onChangeText={() => setState((state) => ({ ...state, filter }))} />
170
- );
240
+ const useIsContactsEmpty = useContactsLength.createSelectorHook((length) => !length);
171
241
  ```
172
242
 
173
- 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.
243
+ It can't get any simpler, right? Everything is connected, everything is reactive. Plus, these hooks are strongly typed, so if you're working with **TypeScript**, you'll absolutely love it.
244
+
245
+ Each selector hook is reactive only to the fragment/derived of the state returned by the selector. And again you can optimize it by using the **isEqualRoot** and **isEqual** functions, which help avoid recomputing the selector if the root state or the fragment hasn't changed.
174
246
 
175
247
  # State actions
176
248
 
@@ -181,30 +253,29 @@ By defining a custom API for the **useContacts**, we can encapsulate and expose
181
253
  ```ts
182
254
  import { createGlobalState } from "react-native-global-state-hooks";
183
255
 
184
- const initialState = {
185
- isLoading: true,
186
- filter: "",
187
- items: [] as Contact[],
188
- };
189
-
190
- type State = typeof initialState;
191
-
192
- export const useContacts = createGlobalState(initialState, {
193
- // this are the actions available for this state
194
- actions: {
195
- setFilter(filter: string) {
196
- return ({ setState }: StoreTools<State>) => {
197
- setState((state) => ({
198
- ...state,
199
- filter,
200
- }));
201
- };
202
- },
203
- } as const,
204
- onInit: async ({ setState }: StoreTools<State>) => {
205
- // fetch contacts
256
+ export const useContacts = createGlobalState(
257
+ {
258
+ isLoading: true,
259
+ filter: "",
260
+ items: [] as Contact[],
206
261
  },
207
- });
262
+ {
263
+ // this are the actions available for this state
264
+ actions: {
265
+ setFilter(filter: string) {
266
+ return ({ setState }) => {
267
+ setState((state) => ({
268
+ ...state,
269
+ filter,
270
+ }));
271
+ };
272
+ },
273
+ } as const,
274
+ onInit: ({ setState }) => {
275
+ // fetch contacts
276
+ },
277
+ }
278
+ );
208
279
  ```
209
280
 
210
281
  That's it! In this updated version, the **useContacts** hook will no longer return [**state**, **stateMutator**] 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.
@@ -237,33 +308,22 @@ It can't get any simpler, right? Everything is connected, everything is reactive
237
308
 
238
309
  # State Controls
239
310
 
240
- 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.
311
+ 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:
241
312
 
242
- Using the state controls 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:
313
+ ```tsx
314
+ useContacts.stateControls: () => [stateRetriever: StateGetter<State>, stateMutator: Setter<State>|ActionCollectionResult<State>, metadataRetriever: Metadata];
243
315
 
244
- ```ts
245
- import { createGlobalState } from "react-native-global-state-hooks";
316
+ // example:
317
+ const [getContacts, setContacts] = useContacts.stateControls();
246
318
 
247
- export const useContacts = createGlobalState({
248
- isLoading: true,
249
- filter: "",
250
- items: [] as Contact[],
251
- });
252
-
253
- export const [contactsRetriever, contactsRetriever] =
254
- useContacts.stateControls();
319
+ console.log(getContacts()); // prints the list of contacts
255
320
  ```
256
321
 
257
- That's great! With the addition of the **contactsRetriever** and **contactsRetriever** methods, you now have the ability to access and modify the state without the need for subscription to the hook.
258
-
259
- 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:
322
+ **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.
260
323
 
261
- ```ts
262
- // To synchronously get the value of the state
263
- const value = contactsRetriever();
324
+ 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.
264
325
 
265
- // the type of value will be { isLoading: boolean; filter: string; items: Contact[] }
266
- ```
326
+ So, 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 and with the **contactsMutator** you now have the ability to modify the state without the need for subscription to the hook.
267
327
 
268
328
  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.
269
329
 
@@ -288,19 +348,162 @@ const removeSubscriptionGroup = contactsRetriever<Subscribe>((subscribe) => {
288
348
 
289
349
  That's great, isn't it? everything stays synchronized with the original state!!
290
350
 
291
- # Emitters
351
+ ## stateMutator
292
352
 
293
- 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:
353
+ Let's add more actions to the state and explore how to use one action from inside another.
354
+
355
+ Here's an example of adding multiple actions to the state and utilizing one action within another:
294
356
 
295
357
  ```ts
296
- const subscribeToFilter = createDerivateEmitter(
297
- contactsRetriever,
298
- ({ filter }) => ({
299
- filter,
300
- })
358
+ import { createGlobalState } from "react-hooks-global-states";
359
+
360
+ export const useCount = createGlobalState(0, {
361
+ actions: {
362
+ log: (currentValue: string) => {
363
+ return ({ getState }: StoreTools<number>): void => {
364
+ console.log(`Current Value: ${getState()}`);
365
+ };
366
+ },
367
+
368
+ increase(value: number = 1) {
369
+ return ({ getState, setState, actions }: StoreTools<number>) => {
370
+ setState((count) => count + value);
371
+
372
+ actions.log(message);
373
+ };
374
+ },
375
+
376
+ decrease(value: number = 1) {
377
+ return ({ getState, setState, actions }: StoreTools<number>) => {
378
+ setState((count) => count - value);
379
+
380
+ actions.log(message);
381
+ };
382
+ },
383
+ } as const,
384
+ });
385
+ ```
386
+
387
+ 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.
388
+
389
+ # Stateful Context with Actions
390
+
391
+ **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...
392
+
393
+ **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.
394
+
395
+ ## Creating a Stateful Context
396
+
397
+ 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.
398
+
399
+ ```tsx
400
+ export const [useCounterContext, CounterProvider] = createStatefulContext(2);
401
+ ```
402
+
403
+ Then just wrap the components you need with the provider:
404
+
405
+ ```tsx
406
+ <CounterProvider>
407
+ <MyComponent />
408
+ </CounterProvider>
409
+ ```
410
+
411
+ And finally, access the context value with the generated custom hook:
412
+
413
+ ```tsx
414
+ const MyComponent = () => {
415
+ const [useCounter] = useCounterContext();
416
+
417
+ // If the component needs to react to state changes, simply use the hook
418
+ const [count, setCount] = useCounter();
419
+
420
+ return <>{count}</>;
421
+ };
422
+ ```
423
+
424
+ 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:
425
+
426
+ ```tsx
427
+ const MyComponent = () => {
428
+ const [, , setCount] = useCounterContext();
429
+
430
+ // This component can access only the stateMutator of the state,
431
+ // and won't re-render if the counter changes
432
+ return <button onClick={() => setCount((count) => count + 1)}>Increase</button>;
433
+ };
434
+ ```
435
+
436
+ Now you have selectors—if the state changes, the component will only re-render if the selected portion of the state changes.
437
+
438
+ ```tsx
439
+ const MyComponent = () => {
440
+ const [useCounter] = useCounterContext();
441
+
442
+ // Notice that we can select and derive values from the state
443
+ const [isEven, setCount] = useCounter((count) => count % 2 === 0);
444
+
445
+ useEffect(() => {
446
+ // Since the counter initially was 2 and now is 4, it’s still an even number.
447
+ // Because of this, the component will not re-render.
448
+ setCount(4);
449
+ }, []);
450
+
451
+ return <>{isEven ? "is even" : "is odd"}</>;
452
+ };
453
+ ```
454
+
455
+ **createStatefulContext** also allows you to add custom actions to control the manipulation of the state.
456
+
457
+ ```tsx
458
+ import { createStatefulContext, StoreTools } from "react-global-state-hooks";
459
+
460
+ export const [useCounterContext, CounterProvider] = createStatefulContext(
461
+ {
462
+ count: 0,
463
+ },
464
+ {
465
+ actions: {
466
+ increase: (value: number = 1) => {
467
+ return ({ setState }) => {
468
+ setState((state) => ({
469
+ ...state,
470
+ count: state.count + value,
471
+ }));
472
+ };
473
+ },
474
+ decrease: (value: number = 1) => {
475
+ return ({ setState }) => {
476
+ setState((state) => ({
477
+ ...state,
478
+ count: state.count - value,
479
+ }));
480
+ };
481
+ },
482
+ } as const,
483
+ }
301
484
  );
302
485
  ```
303
486
 
487
+ And just like with regular global hooks, now instead of a setState function, the hook will return the collection of actions:
488
+
489
+ ```tsx
490
+ const MyComponent = () => {
491
+ const [, , actions] = useCounterContext();
492
+
493
+ return <button onClick={() => actions.increase(1)}>Increase</button>;
494
+ };
495
+ ```
496
+
497
+ # Emitters
498
+
499
+ So, we have seen that we can subscribe a callback to state changes, create **selector hooks** from our global states. Guess what? We can also create derived **emitters** and subscribe callbacks to specific portions of the state. Let's review it:
500
+
501
+ ```ts
502
+ const subscribeToFilter = createDerivateEmitter(contactsRetriever, ({ filter }) => ({
503
+ filter,
504
+ }));
505
+ ```
506
+
304
507
  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.
305
508
 
306
509
  Now we are able to add a callback that will be executed every time the state of the **filter** changes.
@@ -324,7 +527,7 @@ const removeFilterSubscription = subscribeToFilter<Subscribe>(
324
527
  );
325
528
  ```
326
529
 
327
- Also, of course, if you have an exceptional case where you want to derivate directly from the current **emitter**, you can add a **selector**. This allows you to fine-tune the emitted values based on your requirements
530
+ Also, of course, if you have an exceptional case where you want to derived/selected directly from the current **emitter**, you can add a **selector**. This allows you to fine-tune the emitted values based on your requirements
328
531
 
329
532
  ```ts
330
533
  const removeFilterSubscription = subscribeToFilter<Subscribe>(
@@ -350,26 +553,20 @@ const removeFilterSubscription = subscribeToFilter<Subscribe>(
350
553
  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:
351
554
 
352
555
  ```ts
353
- const subscribeToItems = createDerivateEmitter(
354
- contactsRetriever,
355
- ({ items }) => items
356
- );
556
+ const subscribeToItems = createDerivateEmitter(contactsRetriever, ({ items }) => items);
357
557
 
358
- const subscribeToItemsLength = createDerivateEmitter(
359
- subscribeToItems,
360
- (items) => items.length
361
- );
558
+ const subscribeToItemsLength = createDerivateEmitter(subscribeToItems, (items) => items.length);
362
559
  ```
363
560
 
364
- 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!
561
+ The examples may seem a little silly, but they allow you to see the incredible things you can accomplish with these **derived states** and **emitters**. They open up a world of possibilities!
365
562
 
366
- # Combining stateRetrievers
563
+ # Combining stateRetriever
367
564
 
368
- 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 **stateRetrievers**.
565
+ 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**.
369
566
 
370
567
  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:
371
568
 
372
- Fist we are gonna create a couple of **global states**:
569
+ First we are gonna create a couple of **global states**, and extract the **stateRetriever**.
373
570
 
374
571
  ```ts
375
572
  const useHook1 = createGlobalState({
@@ -384,13 +581,13 @@ const useHook2 = createGlobalState({
384
581
  propD: 4,
385
582
  });
386
583
 
387
- const [stateRetriever2] = useHook2.stateControls();
584
+ const [, stateRetriever2] = useHook2.stateControls();
388
585
  ```
389
586
 
390
587
  Okay, cool, the first state as **propA, propB** while the second one has **propC, propD**, let's combine them:
391
588
 
392
589
  ```ts
393
- const [useCombinedHook, stateRetriever, dispose] = combineAsyncGetters(
590
+ const [useCombinedHook, combinedStateRetriever] = combineAsyncGetters(
394
591
  {
395
592
  selector: ([state1, state2]) => ({
396
593
  ...state1,
@@ -402,7 +599,7 @@ const [useCombinedHook, stateRetriever, dispose] = combineAsyncGetters(
402
599
  );
403
600
  ```
404
601
 
405
- 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:
602
+ Well, that's it! Now you have access to a **combinedStateRetriever** that will return the combined value of the two states. From this new **combinedStateRetriever**, you can retrieve the value or subscribe to its changes. Let'see:
406
603
 
407
604
  ```ts
408
605
  const value = stateRetriever(); // { propA, propB, propC, propD }
@@ -417,8 +614,8 @@ const unsubscribeGroup = stateRetriever<Subscribe>((subscribe) => {
417
614
  // the callback will only trigger if the result of the selector changes.
418
615
  subscribe(
419
616
  ({ propA, propD }) => ({ propA, propD }),
420
- (derivate) => {
421
- console.log(derivate); // { propA, propD }
617
+ (derived) => {
618
+ console.log(derived); // { propA, propD }
422
619
  }
423
620
  );
424
621
  });
@@ -440,22 +637,21 @@ Similar to your other **global state hooks**, **combined hooks** allow you to us
440
637
  const [fragment] = useCombinedHook(({ propA, propD }) => ({ propA, propD }));
441
638
  ```
442
639
 
443
- Lastly, you have the flexibility to continue combining stateRetriever if desired. This means you can extend the functionality of combined hooks by adding more stateRetriever to merge additional states. By combining stateRetriever 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.
640
+ 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.
444
641
 
445
642
  Let's see an example:
446
643
 
447
644
  ```ts
448
- const [useCombinedHook, combinedStateRetriever1, dispose1] =
449
- combineAsyncGetters(
450
- {
451
- selector: ([state1, state2]) => ({
452
- ...state1,
453
- ...state2,
454
- }),
455
- },
456
- stateRetriever1,
457
- stateRetriever2
458
- );
645
+ const [useCombinedHook, combinedStateRetriever1] = combineAsyncGetters(
646
+ {
647
+ selector: ([state1, state2]) => ({
648
+ ...state1,
649
+ ...state2,
650
+ }),
651
+ },
652
+ stateRetriever1,
653
+ stateRetriever2
654
+ );
459
655
 
460
656
  const useHook3 = createGlobalState({
461
657
  propE: 1,
@@ -466,13 +662,13 @@ const [stateRetriever3, stateMutator3] = useHook3.stateControls();
466
662
 
467
663
  const useIsLoading = createGlobalState(false);
468
664
 
469
- const [isLoadingGetter, isLoadingSetter] = useIsLoading.stateControls();
665
+ const [isLoadingStateRetriever, isLoadingMutator] = useIsLoading.stateControls();
470
666
  ```
471
667
 
472
668
  Once we created another peace of state, we can combine it with our other **global hooks** and **emitters**
473
669
 
474
670
  ```ts
475
- const [useCombinedHook2, combinedGetter2, dispose2] = combineAsyncGetters(
671
+ const [useCombinedHook2, combinedStateRetriever2] = combineAsyncGetters(
476
672
  {
477
673
  selector: ([state1, state2, isLoading]) => ({
478
674
  ...state1,
@@ -482,7 +678,7 @@ const [useCombinedHook2, combinedGetter2, dispose2] = combineAsyncGetters(
482
678
  },
483
679
  combinedStateRetriever1,
484
680
  stateRetriever3,
485
- isLoadingGetter
681
+ isLoadingStateRetriever
486
682
  );
487
683
  ```
488
684
 
@@ -492,190 +688,6 @@ You have the freedom to combine as many global hooks as you wish. This means you
492
688
 
493
689
  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.
494
690
 
495
- ## Setter
496
-
497
- Similarly, the **contactsRetriever** 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**.
498
-
499
- 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.
500
-
501
- Let's add more actions to the state and explore how to use one action from inside another.
502
-
503
- Here's an example of adding multiple actions to the state and utilizing one action within another:
504
-
505
- ```ts
506
- import { createGlobalState } from "react-native-global-state-hooks";
507
-
508
- export const useCount = createGlobalState(0, {
509
- actions: {
510
- log: (currentValue: string) => {
511
- return ({ getState }: StoreTools<number>): void => {
512
- console.log(`Current Value: ${getState()}`);
513
- };
514
- },
515
-
516
- increase(value: number = 1) {
517
- return ({ getState, setState, actions }: StoreTools<number>) => {
518
- setState((count) => count + value);
519
-
520
- actions.log(message);
521
- };
522
- },
523
-
524
- decrease(value: number = 1) {
525
- return ({ getState, setState, actions }: StoreTools<number>) => {
526
- setState((count) => count - value);
527
-
528
- actions.log(message);
529
- };
530
- },
531
- } as const,
532
- });
533
- ```
534
-
535
- 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.
536
-
537
- ```ts
538
- import { createGlobalState } from "react-native-global-state-hooks";
539
-
540
- export const useCount = createGlobalState(0, {
541
- actions: {
542
- log: (currentValue: string) => {
543
- return ({ getState }: StoreTools<number>): void => {
544
- console.log(`Current Value: ${getState()}`);
545
- };
546
- },
547
-
548
- increase(value: number = 1) {
549
- return ({ getState, setState }: StoreTools<number>) => {
550
- setState((count) => count + value);
551
-
552
- $actions.log(message);
553
- };
554
- },
555
- } as const,
556
- });
557
-
558
- export const [getCount, $actions] = useCount.stateControls();
559
- ```
560
-
561
- In the example the hook will work the same and you'll have access to the correct typing.
562
-
563
- # Stateful Context with Actions
564
-
565
- **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...
566
-
567
- **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.
568
-
569
- ## Creating a Stateful Context
570
-
571
- 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.
572
-
573
- ```tsx
574
- export const [useCounterContext, CounterProvider] = createStatefulContext(2);
575
- ```
576
-
577
- Then just wrap the components you need with the provider:
578
-
579
- ```tsx
580
- <CounterProvider>
581
- <MyComponent />
582
- </CounterProvider>
583
- ```
584
-
585
- And finally, access the context value with the generated custom hook:
586
-
587
- ```tsx
588
- const MyComponent = () => {
589
- const [useCounter] = useCounterContext();
590
-
591
- // If the component needs to react to state changes, simply use the hook
592
- const [count, setCount] = useCounter();
593
-
594
- return <>{count}</>;
595
- };
596
- ```
597
-
598
- 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:
599
-
600
- ```tsx
601
- const MyComponent = () => {
602
- const [, , setCount] = useCounterContext();
603
-
604
- // This component can access only the stateMutator of the state,
605
- // and won't re-render if the counter changes
606
- return (
607
- <button onClick={() => setCount((count) => count + 1)}>Increase</button>
608
- );
609
- };
610
- ```
611
-
612
- Now you have selectors—if the state changes, the component will only re-render if the selected portion of the state changes.
613
-
614
- ```tsx
615
- const MyComponent = () => {
616
- const [useCounter] = useCounterContext();
617
-
618
- // Notice that we can select and derive values from the state
619
- const [isEven, setCount] = useCounter((count) => count % 2 === 0);
620
-
621
- useEffect(() => {
622
- // Since the counter initially was 2 and now is 4, it’s still an even number.
623
- // Because of this, the component will not re-render.
624
- setCount(4);
625
- }, []);
626
-
627
- return <>{isEven ? "is even" : "is odd"}</>;
628
- };
629
- ```
630
-
631
- **createStatefulContext** also allows you to add custom actions to control the manipulation of the state.
632
-
633
- ```tsx
634
- import { createStatefulContext, StoreTools } from "react-global-state-hooks";
635
-
636
- type CounterState = {
637
- count: number;
638
- };
639
-
640
- const initialState: CounterState = {
641
- count: 0,
642
- };
643
-
644
- export const [useCounterContext, CounterProvider] = createStatefulContext(
645
- initialState,
646
- {
647
- actions: {
648
- increase: (value: number = 1) => {
649
- return ({ setState }: StoreTools<CounterState>) => {
650
- setState((state) => ({
651
- ...state,
652
- count: state.count + value,
653
- }));
654
- };
655
- },
656
- decrease: (value: number = 1) => {
657
- return ({ setState }: StoreTools<CounterState>) => {
658
- setState((state) => ({
659
- ...state,
660
- count: state.count - value,
661
- }));
662
- };
663
- },
664
- } as const,
665
- }
666
- );
667
- ```
668
-
669
- And just like with regular global hooks, now instead of a setState function, the hook will return the collection of actions:
670
-
671
- ```tsx
672
- const MyComponent = () => {
673
- const [, , actions] = useCounterContext();
674
-
675
- return <button onClick={() => actions.increase(1)}>Increase</button>;
676
- };
677
- ```
678
-
679
691
  # Extending Global Hooks
680
692
 
681
693
  ## `[IMPORTANT!]`: From version 6.0.0, you can continue creating your custom implementations or using your previous ones. However, now AsyncStorage is already integrated into the global hooks with @react-native-async-storage/async-storage. You simply need to add a key for the persistent storage, and that will do the trick.
@@ -700,7 +712,7 @@ const useCountPersisted = createGlobalState(1, {
700
712
  const [count, setCount, { isAsyncStorageReady }] = useCountPersisted();
701
713
  ```
702
714
 
703
- ##### Now lets continue analizing how to create a custom GlobalStore!
715
+ ##### Now lets continue analyzing how to create a custom GlobalStore!
704
716
 
705
717
  Creating a global hook that connects to an asyncStorage is made incredibly easy with the **createCustomGlobalState** function.
706
718
 
@@ -842,17 +854,17 @@ onInit?: ({
842
854
  /**
843
855
  * @description - callback function called every time the state is changed
844
856
  */
845
- onStateChanged?: (parameters: StateChangesParam<TState, TMetadata, TActions>) => void;
857
+ onStateChanged?: (parameters: StoreTools<any, any> & StateChanges<unknown>) => void;
846
858
 
847
859
  /**
848
860
  * callback function called every time a component is subscribed to the store
849
861
  */
850
- onSubscribed?: (parameters: StateConfigCallbackParam<TState, TMetadata, TActions>) => void;
862
+ onSubscribed?: (parameters: StoreTools<any, any>) => void;
851
863
 
852
864
  /**
853
865
  * callback function called every time the state is about to change and it allows you to prevent the state change
854
866
  */
855
- computePreventStateChange?: (parameters: StateChangesParam<TState, TMetadata, TActions>) => boolean;
867
+ computePreventStateChange?: (parameters: StoreTools<any, any> & StateChanges<unknown>) => boolean;
856
868
  ```
857
869
 
858
870
  You can pass this callbacks between on the second parameter of the builders like **createGlobalState**
@@ -886,9 +898,7 @@ export class GlobalStore<
886
898
  asyncStorageKey?: string;
887
899
  isAsyncStorageReady?: boolean;
888
900
  } | null = null,
889
- TStateSetter extends
890
- | ActionCollectionConfig<TState, TMetadata>
891
- | StateSetter<TState> = StateSetter<TState>
901
+ TStateSetter extends ActionCollectionConfig<TState, TMetadata> | StateSetter<TState> = StateSetter<TState>
892
902
  > extends GlobalStoreAbstract<TState, TMetadata, TStateSetter> {
893
903
  constructor(
894
904
  state: TState,
@@ -936,10 +946,7 @@ export class GlobalStore<
936
946
  setState(items, { forceUpdate: true });
937
947
  };
938
948
 
939
- protected onChange = ({
940
- getMetadata,
941
- getState,
942
- }: StateChangesParam<TState, TMetadata, NonNullable<TStateSetter>>) => {
949
+ protected onChange = ({ getMetadata, getState }: StoreTools<any, any> & StateChanges<unknown>) => {
943
950
  const asyncStorageKey = getMetadata()?.asyncStorageKey;
944
951
 
945
952
  if (!asyncStorageKey) return;
@@ -959,9 +966,8 @@ Then, from an instance of the global store, you will be able to access the hooks
959
966
 
960
967
  ```ts
961
968
  const storage = new GlobalStore(0, {
962
- metadata: {
963
- asyncStorageKey: "counter",
964
- isAsyncStorageReady: false,
969
+ asyncStorage: {
970
+ key: "counter",
965
971
  },
966
972
  });
967
973