react-native-global-state-hooks 8.0.2 → 9.0.1

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 (41) hide show
  1. package/GlobalStore.d.ts +1 -0
  2. package/GlobalStore.js +1 -1
  3. package/GlobalStoreAbstract.d.ts +1 -0
  4. package/GlobalStoreAbstract.js +1 -1
  5. package/README.md +216 -407
  6. package/asyncStorageWrapper.d.ts +1 -1
  7. package/asyncStorageWrapper.js +1 -1
  8. package/bundle.js +1 -1
  9. package/createContext.d.ts +1 -1
  10. package/createContext.js +1 -1
  11. package/createCustomGlobalState.d.ts +1 -0
  12. package/createCustomGlobalState.js +1 -1
  13. package/createGlobalState.d.ts +1 -0
  14. package/createGlobalState.js +1 -1
  15. package/debounce.d.ts +1 -1
  16. package/debounce.js +1 -1
  17. package/getAsyncStorageItem.d.ts +1 -0
  18. package/getAsyncStorageItem.js +1 -1
  19. package/index.d.ts +1 -3
  20. package/isRecord.d.ts +1 -1
  21. package/isRecord.js +1 -1
  22. package/package.json +3 -13
  23. package/setAsyncStorageItem.d.ts +1 -0
  24. package/setAsyncStorageItem.js +1 -1
  25. package/shallowCompare.d.ts +1 -1
  26. package/shallowCompare.js +1 -1
  27. package/throwWrongKeyOnActionCollectionConfig.d.ts +1 -1
  28. package/throwWrongKeyOnActionCollectionConfig.js +1 -1
  29. package/uniqueId.d.ts +1 -1
  30. package/uniqueId.js +1 -1
  31. package/uniqueSymbol.d.ts +1 -1
  32. package/uniqueSymbol.js +1 -1
  33. package/useStableState.d.ts +1 -0
  34. package/useStableState.js +1 -0
  35. package/webpack.config.js +1 -3
  36. package/combineRetrieverAsynchronously.d.ts +0 -1
  37. package/combineRetrieverAsynchronously.js +0 -1
  38. package/combineRetrieverEmitterAsynchronously.d.ts +0 -1
  39. package/combineRetrieverEmitterAsynchronously.js +0 -1
  40. package/useConstantValueRef.d.ts +0 -1
  41. package/useConstantValueRef.js +0 -1
package/README.md CHANGED
@@ -2,88 +2,95 @@
2
2
 
3
3
  ![Image John Avatar](https://raw.githubusercontent.com/johnny-quesada-developer/global-hooks-example/main/public/avatar2.jpeg)
4
4
 
5
- Hi There! Welcome to **react-native-global-state-hooks** your New State Management Solution for React Native Components 🚀
5
+ Effortless **global state management** for React & React Native! 🚀 Define a **global state in just one line of code** and enjoy **lightweight, flexible, and scalable** state management. Try it now on **[CodePen](https://codepen.io/johnnynabetes/pen/WNmeGwb?editors=0010)** and see it in action! ✨
6
6
 
7
- Are you looking for a solution to manage **global state** in your **React Native components**? Look no further!
7
+ ---
8
8
 
9
- **react-native-global-state-hooks** is your option for efficiently handling global state management in your React Native applications.
9
+ ## 🔗 Explore More
10
10
 
11
- One line of code for a **global state**! try it out now on [CODEPEN-react-global-state-hooks](https://codepen.io/johnnynabetes/pen/WNmeGwb?editors=0010) and witness the magic ✨.
11
+ - **[Live Example](https://johnny-quesada-developer.github.io/react-global-state-hooks-example/)** 📘
12
+ - **[React Native Integration](https://www.npmjs.com/package/react-native-global-state-hooks/)** 📱
13
+ - **[Todo-List Example](https://github.com/johnny-quesada-developer/todo-list-with-global-hooks.git/)** 📝
14
+ - **[Video Overview](https://www.youtube.com/watch?v=1UBqXk2MH8I/)** 🎥
15
+ - **[GitHub Repository](https://github.com/johnny-quesada-developer/global-hooks-example/)** 🧩
12
16
 
13
- For a deeper dive into how these hooks work, check out a comprehensive example at [react-global-state-hooks-example](https://johnny-quesada-developer.github.io/react-global-state-hooks-example/) 📘.
17
+ Works seamlessly with **React & React Native**:
14
18
 
15
- You can explore a **TODO-LIST** example using global state and asynchronous storage by heading to [todo-list-with-global-hooks](https://github.com/johnny-quesada-developer/todo-list-with-global-hooks.git) 📝.
19
+ - **[react-global-state-hooks](https://www.npmjs.com/package/react-global-state-hooks)** for web applications.
20
+ - **[react-native-global-state-hooks](https://www.npmjs.com/package/react-native-global-state-hooks)** for React Native projects.
16
21
 
17
- For a more visual introduction, watch our informative video [here!](https://www.youtube.com/watch?v=1UBqXk2MH8I) 🎥 and dive into the code on [global-hooks-example](https://github.com/johnny-quesada-developer/global-hooks-example) 🧩.
22
+ ---
18
23
 
19
- The best part? [react-hooks-global-states](https://www.npmjs.com/package/react-hooks-global-states) is compatible with both **React** and **React Native**. If you're building web applications, explore this [react-global-state-hooks](https://www.npmjs.com/package/react-global-state-hooks), and for your React Native projects, here you are at **react-native-global-state-hooks**. These specialized libraries extend the capabilities of [react-hooks-global-states](https://www.npmjs.com/package/react-hooks-global-states) to perfectly fit your specific development environments. Discover the ease of global state management today! 🌐
24
+ ## 🗂️ Async Persist Storage
20
25
 
21
- # Creating a global state
22
-
23
- We are gonna create a global state hook **useCount** with one line of code.
26
+ To persist the global state using **Async Storage**, simply add the `asyncStorage` option:
24
27
 
25
28
  ```ts
26
- import { createGlobalState } from "react-native-global-state-hooks/createGlobalState";
27
-
28
- export const useCount = createGlobalState(0);
29
+ const useCount = createGlobalState(0, {
30
+ asyncStorage: {
31
+ key: "count",
32
+ },
33
+ });
29
34
  ```
30
35
 
31
- That's it! Welcome to global hooks. Now, you can use this state wherever you need it in your application.
36
+ ### 🔹 How It Works
32
37
 
33
- Let's see how to use it inside a simple **component**
38
+ **Automatically syncs the state with Async Storage** if the value is serializable.
39
+ ✅ **Provides an `isAsyncStorageReady` flag** to indicate when the async storage has been reviewed and committed.
40
+ ✅ **Uses `@react-native-async-storage/async-storage` by default** (make sure to install this package if needed).
41
+ ✅ **Allows custom async storage managers** with `asyncStorageWrapper.addAsyncStorageManager(customAsyncStorageManager)`;
34
42
 
35
- ```ts
36
- const [count, setCount] = useCount();
43
+ Inside your components:
37
44
 
38
- return <Button onClick={() => setCount((count) => count + 1)}>{count}</Button>;
45
+ ```tsx
46
+ const [count, setCount, { isAsyncStorageReady }] = useCount();
39
47
  ```
40
48
 
41
- Isn't it cool? It works just like a regular **useState**. Notice the only difference is that now you don't need to provide the initial value since this is a global hook, and the initial value has already been provided.
49
+ If you specify a key in `asyncStorage`, the state value persists automatically when serializable. When connecting to async storage, expect a **second render** that updates `isAsyncStorageReady`, indicating that the storage has been reviewed and the state is committed.
42
50
 
43
- # Using async persist storage
51
+ ### 🔧 Custom Async Storage Manager
52
+
53
+ You can configure your own storage selection by using `asyncStorageWrapper.addAsyncStorageManager`. Ensure that the manager is **added before any hook is called**.
44
54
 
45
- Just add a key for the async storage
55
+ `index.ts`
46
56
 
47
57
  ```ts
48
- const useCountPersisted = createGlobalState(1, {
49
- asyncStorage: {
50
- key: "count",
51
- },
52
- });
58
+ import { asyncStorageWrapper } from "react-global-state-hooks";
59
+ asyncStorageWrapper.addAsyncStorageManager(customAsyncStorageManager);
53
60
  ```
54
61
 
55
- After this the metadata of the hooks will now include a flag which will help you to determinate if the async storage was already reached.
62
+ ## 🛠 Creating a Global State
56
63
 
57
- ```ts
58
- const [count, setCount, { isAsyncStorageReady }] = useCountPersisted();
64
+ Define a **global state** in **one line**:
65
+
66
+ ```tsx
67
+ import { createGlobalState } from "react-hooks-global-states/createGlobalState";
68
+ export const useCount = createGlobalState(0);
59
69
  ```
60
70
 
61
- If no key is provided, the default metadata is null. Otherwise, it's set to **{ isAsyncStorageReady: false }**.
71
+ Now, use it inside a component:
72
+
73
+ ```tsx
74
+ const [count, setCount] = useCount();
75
+ return <Button onClick={() => setCount((count) => count + 1)}>{count}</Button>;
76
+ ```
62
77
 
63
- Upon the first successful retrieval from **AsyncStorage**, components will re-render with **{ isAsyncStorageReady: true }** in the metadata.
64
- The metadata and components will be updated and re-rendered even if there's no difference between the value stored in **AsyncStorage** and the store's default value. This is the only instance where the metadata forces a re-render, after this the metadata will not update the component
78
+ Works just like **useState**, but the **state is shared globally**! 🎉
65
79
 
66
- # Selectors
80
+ ---
67
81
 
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**
82
+ ## 🎯 Selectors: Subscribing to Specific State Changes
69
83
 
70
- ```ts
71
- import { createGlobalState } from 'react-hooks-global-states';
84
+ For **complex state objects**, you can subscribe to specific properties instead of the entire state:
72
85
 
73
- export const useContacts = createGlobalState({
74
- isLoading: true,
75
- entities: Contact[],
76
- selected: Set<number>,
77
- });
86
+ ```tsx
87
+ export const useContacts = createGlobalState({ entities: [], selected: new Set<number>() });
78
88
  ```
79
89
 
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.
90
+ To access only the `entities` property:
81
91
 
82
92
  ```tsx
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]);
86
-
93
+ const [contacts] = useContacts((state) => state.entities);
87
94
  return (
88
95
  <ul>
89
96
  {contacts.map((contact) => (
@@ -93,312 +100,152 @@ return (
93
100
  );
94
101
  ```
95
102
 
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?
103
+ ### 📌 Using Dependencies in Selectors
114
104
 
115
- **component.ts**
105
+ You can also add **dependencies** to a selector. This is useful when you want to derive state based on another piece of state (e.g., a filtered list). For example, if you're filtering contacts based on a `filter` value:
116
106
 
117
107
  ```tsx
118
- const [filter, setFilter] = useState("");
119
-
120
108
  const [contacts] = useContacts(
121
- (state) => [...state.entities.values()].filter((item) => item.name.includes(filter)),
122
- {
123
- isEqualRoot: (a, b) => a.entities === b.entities,
124
- /**
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.*/
128
- dependencies: [filter],
129
- }
109
+ (state) => state.entities.filter((item) => item.name.includes(filter)),
110
+ [filter]
130
111
  );
131
112
  ```
132
113
 
133
- And finally, what if you need to reuse this selector throughout your application and don't want to duplicate code?
114
+ Alternatively, you can pass dependencies inside an **options object**:
134
115
 
135
116
  ```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()], {
117
+ const [contacts] = useContacts((state) => state.entities.filter((item) => item.name.includes(filter)), {
118
+ dependencies: [filter],
143
119
  isEqualRoot: (a, b) => a.entities === b.entities,
144
120
  });
145
121
  ```
146
122
 
147
- Now inside your component just call the new hook
148
-
149
- **component.ts**
123
+ Unlike Redux, where only **root state changes trigger re-selection**, this approach ensures that **derived values recompute when dependencies change** while maintaining performance.
150
124
 
151
- ```tsx
152
- const [filter, setFilter] = useState("");
125
+ ---
153
126
 
154
- const [contacts] = useContactsArray((entities) => entities.name.includes(filter), {
155
- dependencies: [filter],
156
- });
157
- ```
127
+ ## 🔄 Reusing Selectors
158
128
 
159
- Or you can create another selectorHook from your **useContactsArray**
129
+ ### 📌 Creating a Selector
160
130
 
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);
131
+ ```tsx
132
+ export const useContactsArray = useContacts.createSelectorHook((state) => state.entities);
133
+ export const useContactsCount = useContactsArray.createSelectorHook((entities) => entities.length);
167
134
  ```
168
135
 
169
- Or you can create a custom hook
136
+ ### 📌 Using Selectors in Components
170
137
 
171
138
  ```tsx
172
- const useFilteredContacts = (filter: string) => {
173
- const [contacts] = useContactsArray((entities) => entities.name.includes(filter), {
174
- dependencies: [filter],
175
- });
176
-
177
- return contacts;
178
- };
139
+ const [contacts] = useContactsArray();
140
+ const [count] = useContactsCount();
179
141
  ```
180
142
 
181
- To summarize
143
+ #### ✅ Selectors support inline selectors and dependencies
182
144
 
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
- });
145
+ You can still **use dependencies** inside a selector hook:
208
146
 
209
- return (
210
- <ul>
211
- {contacts.map((contact) => (
212
- <li key={contact.id}>{contact.name}</li>
213
- ))}
214
- </ul>
147
+ ```tsx
148
+ const [filteredContacts] = useContactsArray(
149
+ (contacts) => contacts.filter((c) => c.name.includes(filter)),
150
+ [filter]
215
151
  );
216
152
  ```
217
153
 
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.
219
-
220
- ```TSX
221
- ({
222
- /**
223
- * You can use the `shallowCompare` from the GlobalStore.utils to compare the values at first level.
224
- */
225
- isEqual: shallowCompare,
226
- })
227
- ```
228
-
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.
230
-
231
- More examples:
154
+ #### Selector hooks share the same state mutator
232
155
 
233
- ```ts
234
- const useFilter = useContacts.createSelectorHook(({ filter }) => filter);
156
+ The **stateMutator remains the same** across all derived selectors, meaning actions and setState functions stay consistent.
235
157
 
236
- const useContactsArray = useContacts.createSelectorHook(({ items }) => items);
237
-
238
- const useContactsLength = useContactsArray.createSelectorHook((items) => items.length);
158
+ ```tsx
159
+ const [actions1] = useContactsArray();
160
+ const [actions2] = useContactsCount();
239
161
 
240
- const useIsContactsEmpty = useContactsLength.createSelectorHook((length) => !length);
162
+ console.log(actions1 === actions2); // true
241
163
  ```
242
164
 
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.
165
+ ---
246
166
 
247
- # State actions
167
+ ## 🎛 State Actions: Controlling State Modifications
248
168
 
249
- Is common and often necessary to restrict the manipulation of state to a specific set of actions or operations. To achieve this, we can simplify the process by adding a custom API to the configuration of our **useContacts**.
250
-
251
- By defining a custom API for the **useContacts**, we can encapsulate and expose only the necessary actions or operations that are allowed to modify the state. This provides a controlled interface for interacting with the state, ensuring that modifications stick to the desired restrictions.
252
-
253
- ```ts
254
- import { createGlobalState } from "react-native-global-state-hooks";
169
+ Restrict **state modifications** by defining custom actions:
255
170
 
171
+ ```tsx
256
172
  export const useContacts = createGlobalState(
173
+ { filter: "", items: [] },
257
174
  {
258
- isLoading: true,
259
- filter: "",
260
- items: [] as Contact[],
261
- },
262
- {
263
- // this are the actions available for this state
264
175
  actions: {
176
+ async fetch() {
177
+ return async ({ setState }) => {
178
+ const items = await fetchItems();
179
+ setState({ items });
180
+ };
181
+ },
265
182
  setFilter(filter: string) {
266
183
  return ({ setState }) => {
267
- setState((state) => ({
268
- ...state,
269
- filter,
270
- }));
184
+ setState((state) => ({ ...state, filter }));
271
185
  };
272
186
  },
273
- } as const,
274
- onInit: ({ setState }) => {
275
- // fetch contacts
276
187
  },
277
188
  }
278
189
  );
279
190
  ```
280
191
 
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.
282
-
283
- Let's see how that will look now into our **FilterBar.tsx**
192
+ Now, instead of `setState`, the hook returns **actions**:
284
193
 
285
194
  ```tsx
286
- const [{ filter }, { setFilter }] = useFilter();
287
-
288
- return <TextInput onChangeText={setFilter} />;
195
+ const [filter, { setFilter }] = useContacts();
289
196
  ```
290
197
 
291
- Yeah, that's it! All the **derived states** and **emitters** (we will talk about this later) will inherit the new actions interface.
198
+ ---
292
199
 
293
- You can even **derive** from another **derived state**! Let's explore a few silly examples:
294
-
295
- ```ts
296
- const useFilter = createDerivate(useContacts, ({ filter }) => ({ filter }));
200
+ ## 🌍 Accessing Global State Outside Components
297
201
 
298
- const useFilterString = createDerivate(useFilter, { filter } => filter);
299
-
300
- const useContacts = createDerivate(useContacts, ({ items }) => items);
301
-
302
- const useContactsLength = createDerivate(useContacts, (items) => items.length);
303
-
304
- const useIsContactsEmpty = createDerivate(useContactsLength, (length) => !length);
305
- ```
306
-
307
- 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.
308
-
309
- # State Controls
310
-
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:
202
+ Use `stateControls()` to **retrieve or update state outside React components**:
312
203
 
313
204
  ```tsx
314
- useContacts.stateControls: () => [stateRetriever: StateGetter<State>, stateMutator: Setter<State>|ActionCollectionResult<State>, metadataRetriever: Metadata];
315
-
316
- // example:
317
- const [getContacts, setContacts] = useContacts.stateControls();
318
-
319
- console.log(getContacts()); // prints the list of contacts
205
+ const [contactsRetriever, contactsApi] = useContacts.stateControls();
206
+ console.log(contactsRetriever()); // Retrieves the current state
320
207
  ```
321
208
 
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.
323
-
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.
325
-
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.
209
+ #### Subscribe to changes
327
210
 
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.
329
-
330
- ```ts
331
- /**
332
- * This not only allows you to retrieve the current value of the state...
333
- * but also enables you to subscribe to any changes in the state or a portion of it
334
- */
335
- const unsubscribe1 = contactsRetriever((state) => {
336
- console.log("state changed: ", state);
211
+ ```tsx
212
+ const unsubscribe = contactsRetriever((state) => {
213
+ console.log("State updated:", state);
337
214
  });
338
-
339
- const unsubscribe2 = contactsRetriever(
340
- (state) => state.isLoading,
341
- (isLoading) => {
342
- console.log("is loading changed", isLoading);
343
- }
344
- );
345
215
  ```
346
216
 
347
- That's great, isn't it? everything stays synchronized with the original state!!
217
+ #### Subscriptions are great when one state depends on another.
348
218
 
349
- ## stateMutator
350
-
351
- Let's add more actions to the state and explore how to use one action from inside another.
352
-
353
- Here's an example of adding multiple actions to the state and utilizing one action within another:
354
-
355
- ```ts
356
- import { createGlobalState } from "react-hooks-global-states";
357
-
358
- export const useCount = createGlobalState(0, {
359
- actions: {
360
- log: (currentValue: string) => {
361
- return ({ getState }: StoreTools<number>): void => {
362
- console.log(`Current Value: ${getState()}`);
363
- };
364
- },
365
-
366
- increase(value: number = 1) {
367
- return ({ getState, setState, actions }: StoreTools<number>) => {
368
- setState((count) => count + value);
369
-
370
- actions.log(message);
371
- };
372
- },
373
-
374
- decrease(value: number = 1) {
375
- return ({ getState, setState, actions }: StoreTools<number>) => {
376
- setState((count) => count - value);
377
-
378
- actions.log(message);
379
- };
219
+ ```tsx
220
+ const useSelectedContact = createGlobalState(null, {
221
+ callbacks: {
222
+ onInit: ({ setState, getState }) => {
223
+ contactsRetriever(
224
+ (state) => state.contacts,
225
+ (contacts) => {
226
+ if (!contacts.has(getState())) setState(null);
227
+ }
228
+ );
380
229
  },
381
- } as const,
230
+ },
382
231
  });
383
232
  ```
384
233
 
385
- 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.
386
-
387
- # Stateful Context with Actions
388
-
389
- **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...
234
+ ---
390
235
 
391
- **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.
236
+ ## 🎭 Using Context for Scoped State
392
237
 
393
- ## Creating a Stateful Context
238
+ - **Scoped State** Context state is **isolated inside the provider**.
239
+ - **Same API** – Context supports **selectors, actions, and state controls**.
394
240
 
395
- 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.
241
+ ### 📌 Creating a Context
396
242
 
397
243
  ```tsx
398
- export const [useCounterContext, CounterProvider] = createStatefulContext(2);
244
+ import { createContext } from "react-global-state-hooks/createContext";
245
+ export const [useCounterContext, CounterProvider] = createContext(0);
399
246
  ```
400
247
 
401
- Then just wrap the components you need with the provider:
248
+ Wrap your app:
402
249
 
403
250
  ```tsx
404
251
  <CounterProvider>
@@ -406,169 +253,131 @@ Then just wrap the components you need with the provider:
406
253
  </CounterProvider>
407
254
  ```
408
255
 
409
- And finally, access the context value with the generated custom hook:
256
+ Use the context state:
410
257
 
411
258
  ```tsx
412
- const MyComponent = () => {
413
- const [useCounter] = useCounterContext();
259
+ const [count] = useCounterContext();
260
+ ```
414
261
 
415
- // If the component needs to react to state changes, simply use the hook
416
- const [count, setCount] = useCounter();
262
+ ### 📌 Context Selectors
417
263
 
418
- return <>{count}</>;
419
- };
420
- ```
264
+ Works **just like global state**, but within the provider.
421
265
 
422
- 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:
266
+ ---
267
+
268
+ ## 🔥 Observables: Watching State Changes
269
+
270
+ Observables **let you react to state changes** via subscriptions.
271
+
272
+ ### 📌 Creating an Observable
423
273
 
424
274
  ```tsx
425
- const MyComponent = () => {
426
- const [, , setCount] = useCounterContext();
275
+ export const useCounter = createGlobalState(0);
276
+ export const counterLogs = useCounter.createObservable((count) => `Counter is at ${count}`);
277
+ ```
278
+
279
+ ### 📌 Subscribing to an Observable
427
280
 
428
- // This component can access only the stateMutator of the state,
429
- // and won't re-render if the counter changes
430
- return <button onClick={() => setCount((count) => count + 1)}>Increase</button>;
431
- };
281
+ ```tsx
282
+ const unsubscribe = counterLogs((message) => {
283
+ console.log(message);
284
+ });
432
285
  ```
433
286
 
434
- Now you have selectors—if the state changes, the component will only re-render if the selected portion of the state changes.
287
+ ### 📌 Using Observables Inside Context
435
288
 
436
289
  ```tsx
437
- const MyComponent = () => {
438
- const [useCounter] = useCounterContext();
290
+ export const [useStateControls, useObservableBuilder] = useCounterContext.stateControls();
291
+ const createObservable = useObservableBuilder();
292
+ useEffect(() => {
293
+ const unsubscribe = createObservable((count) => {
294
+ console.log(`Updated count: ${count}`);
295
+ });
296
+ return unsubscribe;
297
+ }, []);
298
+ ```
439
299
 
440
- // Notice that we can select and derive values from the state
441
- const [isEven, setCount] = useCounter((count) => count % 2 === 0);
300
+ ---
442
301
 
443
- useEffect(() => {
444
- // Since the counter initially was 2 and now is 4, it’s still an even number.
445
- // Because of this, the component will not re-render.
446
- setCount(4);
447
- }, []);
302
+ ## ⚖️ `createGlobalState` vs. `createContext`
448
303
 
449
- return <>{isEven ? "is even" : "is odd"}</>;
450
- };
451
- ```
304
+ | Feature | `createGlobalState` | `createContext` |
305
+ | ---------------------- | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
306
+ | **Scope** | Available globally across the entire app | Scoped to the Provider where it’s used |
307
+ | **How to Use** | `const useCount = createGlobalState(0)` | `const [useCountContext, Provider] = createContext(0)` |
308
+ | **createSelectorHook** | `useCount.createSelectorHook` | `useCountContext.createSelectorHook` |
309
+ | **inline selectors?** | ✅ Supported | ✅ Supported |
310
+ | **Custom Actions** | ✅ Supported | ✅ Supported |
311
+ | **Observables** | `useCount.createObservable` | `const [, useObservableBuilder] = useCountContext.stateControls()` |
312
+ | **State Controls** | `useCount.stateControls()` | `const [useStateControls] = useCountContext.stateControls()` |
313
+ | **Best For** | Global app state (auth, settings, cache) | Scoped module state, reusable component state, or state shared between child components without being fully global |
452
314
 
453
- **createStatefulContext** also allows you to add custom actions to control the manipulation of the state.
315
+ ## 🔄 Lifecycle Methods
454
316
 
455
- ```tsx
456
- import { createStatefulContext, StoreTools } from "react-global-state-hooks";
317
+ Global state hooks support lifecycle callbacks for additional control.
457
318
 
458
- export const [useCounterContext, CounterProvider] = createStatefulContext(
459
- {
460
- count: 0,
461
- },
319
+ ```tsx
320
+ const useData = createGlobalState(
321
+ { value: 1 },
462
322
  {
463
- actions: {
464
- increase: (value: number = 1) => {
465
- return ({ setState }) => {
466
- setState((state) => ({
467
- ...state,
468
- count: state.count + value,
469
- }));
470
- };
323
+ callbacks: {
324
+ onInit: ({ setState }) => {
325
+ console.log("Store initialized");
471
326
  },
472
- decrease: (value: number = 1) => {
473
- return ({ setState }) => {
474
- setState((state) => ({
475
- ...state,
476
- count: state.count - value,
477
- }));
478
- };
327
+ onStateChanged: ({ state, previousState }) => {
328
+ console.log("State changed:", previousState, "→", state);
329
+ },
330
+ computePreventStateChange: ({ state, previousState }) => {
331
+ return state.value === previousState.value;
479
332
  },
480
- } as const,
333
+ },
481
334
  }
482
335
  );
483
336
  ```
484
337
 
485
- And just like with regular global hooks, now instead of a setState function, the hook will return the collection of actions:
338
+ Use **`onInit`** for setup, **`onStateChanged`** to listen to updates, and **`computePreventStateChange`** to prevent unnecessary updates.
486
339
 
487
- ```tsx
488
- const MyComponent = () => {
489
- const [, , actions] = useCounterContext();
340
+ ## Metadata
341
+
342
+ There is a possibility to add non reactive information in the global state:
490
343
 
491
- return <button onClick={() => actions.increase(1)}>Increase</button>;
492
- };
344
+ ```tsx
345
+ const useCount = createGlobalState(0, { metadata: { renders: 0 } });
493
346
  ```
494
347
 
495
- # Life cycle methods
348
+ How to use it?
496
349
 
497
- There are some lifecycle methods available for use with global hooks, let's review them:
350
+ ```tsx
351
+ const [count, , metadata] = useCount();
498
352
 
499
- ```ts
500
- /**
501
- * @description callback function called when the store is initialized
502
- * @returns {void} result - void
503
- * */
504
- onInit?: ({
505
- /**
506
- * Set the metadata
507
- * @param {TMetadata} setter - The metadata or a function that will receive the metadata and return the new metadata
508
- * */
509
- setMetadata: MetadataSetter<TMetadata>;
510
-
511
- /**
512
- * Set the state
513
- * @param {TState} setter - The state or a function that will receive the state and return the new state
514
- * @param {{ forceUpdate?: boolean }} options - Options
515
- * */
516
- setState: StateSetter<TState>;
517
-
518
- /**
519
- * Get the state
520
- * @returns {TState} result - The state
521
- * */
522
- getState: () => TState;
523
-
524
- /**
525
- * Get the metadata
526
- * @returns {TMetadata} result - The metadata
527
- * */
528
- getMetadata: () => TMetadata;
529
-
530
- /**
531
- * Actions of the hook if configuration was provided
532
- */
533
- actions: TActions;
534
- }: StateConfigCallbackParam<TState, TMetadata, TActions>) => void;
535
-
536
- /**
537
- * @description - callback function called every time the state is changed
538
- */
539
- onStateChanged?: (parameters: StoreTools<any, any> & StateChanges<unknown>) => void;
540
-
541
- /**
542
- * callback function called every time a component is subscribed to the store
543
- */
544
- onSubscribed?: (parameters: StoreTools<any, any>) => void;
545
-
546
- /**
547
- * callback function called every time the state is about to change and it allows you to prevent the state change
548
- */
549
- computePreventStateChange?: (parameters: StoreTools<any, any> & StateChanges<unknown>) => boolean;
353
+ metadata.renders += 1;
550
354
  ```
551
355
 
552
- You can pass this callbacks between on the second parameter of the builders like **createGlobalState**
356
+ ## 🎯 Ready to Try It?
357
+
358
+ 📦 **NPM Package:** [react-hooks-global-states](https://www.npmjs.com/package/react-hooks-global-states)
359
+
360
+ 🚀 Simplify your **global state management** in React & React Native today! 🚀
361
+
362
+ # Using async persist storage
553
363
 
554
364
  ```ts
555
- const useData = createGlobalState(
556
- { value: 1 },
557
- {
558
- metadata: {
559
- someExtraInformation: "someExtraInformation",
560
- },
561
- callbacks: {
562
- // onSubscribed: (StateConfigCallbackParam) => {},
563
- // onInit // etc
564
- computePreventStateChange: ({ state, previousState }) => {
565
- const prevent = isEqual(state, previousState);
365
+ const useCount = createGlobalState(0, {
366
+ asyncStorage: {
367
+ key: "count",
368
+ },
369
+ });
370
+ ```
566
371
 
567
- return prevent;
568
- },
569
- },
570
- }
571
- );
372
+ Inside your components
373
+
374
+ ```tsx
375
+ const [count, setCount, { isAsyncStorageReady }] = useCount();
572
376
  ```
573
377
 
574
- # That's it for now!! hope you enjoy coding!!
378
+ - if you specify a key into the `asyncStorage` this will persist the state value if the same is serializable
379
+ - when connecting to the async storage you can expect a second render which will update isAsyncStorageReady indicating that the async storage was already reviewed and the state value is committed.
380
+
381
+ The async storage default functionality depends on **@react-native-async-storage/async-storage** but this dependency is optional, install the package as a dependency if you want to enable persisted state.
382
+
383
+ optionally you can configure your own selection for persisting storage by using `asyncStorageWrapper.addAsyncStorageManager`, notice that the manager should be added before any hook gets call;