react-global-state-hooks 8.0.12 β†’ 8.0.14

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 (2) hide show
  1. package/README.md +175 -426
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,94 +1,70 @@
1
- # react-global-state-hooks 🌟
1
+ # react-hooks-global-states 🌟
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-global-state-hooks** your New State Management Solution for React 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 components**? Look no further!
7
+ ---
8
8
 
9
- **react-global-state-hooks** is your option for efficiently handling global state management in your React 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
- Want to explore how it works with **React Native**? Head over to [react-native-global-state-hooks](https://www.npmjs.com/package/react-native-global-state-hooks) for a hands-on experience πŸ“±. You can also 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** is compatible with both **React** and **React Native**. If you're building web applications, here you are **react-global-state-hooks**, and for your React Native projects, check out [**react-native-global-state-hooks**](https://www.npmjs.com/package/react-native-global-state-hooks). These specialized libraries extend the capabilities of **react-hooks-global-states** to perfectly fit your specific development environments. Discover the ease of global state management today! 🌐
24
+ ## πŸ—‚οΈ Persisting State with LocalStorage
20
25
 
21
- # Creating a global state
26
+ To persist the global state using **LocalStorage**, simply add the `localStorage` option:
22
27
 
23
- We are gonna create a global state hook **useCount** with one line of code.
24
-
25
- ```ts
26
- import { createGlobalState } from 'react-global-state-hooks/createGlobalState';
27
-
28
- export const useCount = createGlobalState(0);
28
+ ```tsx
29
+ const useContacts = createGlobalState(new Map(), {
30
+ localStorage: {
31
+ key: 'contacts',
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.
32
-
33
- The library is builded in modules, you can import everything or only what you need.
36
+ ## πŸ›  Creating a Global State
34
37
 
35
- ```ts
36
- import { createGlobalState } from 'react-global-state-hooks/createGlobalState';
38
+ Define a **global state** in **one line**:
37
39
 
40
+ ```tsx
41
+ import { createGlobalState } from 'react-hooks-global-states/createGlobalState';
38
42
  export const useCount = createGlobalState(0);
39
43
  ```
40
44
 
41
- Let's see how to use it inside a simple **component**
45
+ Now, use it inside a component:
42
46
 
43
- ```ts
47
+ ```tsx
44
48
  const [count, setCount] = useCount();
45
-
46
- return <button onclick={() => setCount((count) => count + 1)}>{count}</button>;
49
+ return <Button onClick={() => setCount((count) => count + 1)}>{count}</Button>;
47
50
  ```
48
51
 
49
- 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.
50
-
51
- # Local Storage
52
-
53
- By default, our global hooks are capable of persisting information in local storage. To achieve this, you need to provide the key that will be used to persist the data. Additionally, you have the option to encrypt the stored data.
54
-
55
- ```ts
56
- const useContacts = createGlobalState(
57
- {
58
- filter: '',
59
- items: [] as Contact[],
60
- },
61
- {
62
- localStorage: {
63
- key: 'data',
64
- },
65
- }
66
- );
67
- ```
52
+ Works just like **useState**, but the **state is shared globally**! πŸŽ‰
68
53
 
69
- That means your data will automatically be synchronized with the local storage, and you don't need to worry about losing the type of your Maps/Sets/Dates - the store takes care of it for you.
54
+ ---
70
55
 
71
- # Selectors
56
+ ## 🎯 Selectors: Subscribing to Specific State Changes
72
57
 
73
- 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**
58
+ For **complex state objects**, you can subscribe to specific properties instead of the entire state:
74
59
 
75
- ```ts
76
- import { createGlobalState } from 'react-hooks-global-states';
77
-
78
- export const useContacts = createGlobalState({
79
- isLoading: true,
80
- entities: Contact[],
81
- selected: Set<number>,
82
- });
60
+ ```tsx
61
+ export const useContacts = createGlobalState({ entities: [], selected: new Set<number>() });
83
62
  ```
84
63
 
85
- 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.
64
+ To access only the `entities` property:
86
65
 
87
66
  ```tsx
88
- // That's it. With that simple selector, we now get the list of contacts,
89
- // and the component will only re-render if the property **entities** changes on the global state
90
- const [contacts] = useContacts((state) => state.entities]);
91
-
67
+ const [contacts] = useContacts((state) => state.entities);
92
68
  return (
93
69
  <ul>
94
70
  {contacts.map((contact) => (
@@ -98,186 +74,88 @@ return (
98
74
  );
99
75
  ```
100
76
 
101
- 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.
102
-
103
- ```tsx
104
- export const useContacts = createGlobalState({
105
- isLoading: true,
106
- entities: Map<number, Contact>,
107
- selected: Set<number>,
108
- });
77
+ ### πŸ“Œ Using Dependencies in Selectors
109
78
 
110
- // The selector is simply a standard selector used to extract the values from the map.
111
-
112
- const [contacts] = useContacts((state) => [...state.entities.values()], {
113
- // The isEqualRoots function allows you to create your own validation logic for determining when to recompute the selector.
114
- isEqualRoot: (a, b) => a.entities === b.entities,
115
- });
116
- ```
117
-
118
- 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?
119
-
120
- **component.ts**
79
+ 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:
121
80
 
122
81
  ```tsx
123
- const [filter, setFilter] = useState('');
124
-
125
82
  const [contacts] = useContacts(
126
- (state) => [...state.entities.values()].filter((item) => item.name.includes(filter)),
127
- {
128
- isEqualRoot: (a, b) => a.entities === b.entities,
129
- /**
130
- * Easy to understand, right? With the dependencies prop, you can,
131
- * just like with any other hook, provide a collection of values that will be compared during each render cycle
132
- * to determine if the selector should be recomputed.*/
133
- dependencies: [filter],
134
- }
83
+ (state) => state.entities.filter((item) => item.name.includes(filter)),
84
+ [filter]
135
85
  );
136
86
  ```
137
87
 
138
- And finally, what if you need to reuse this selector throughout your application and don't want to duplicate code?
88
+ Alternatively, you can pass dependencies inside an **options object**:
139
89
 
140
90
  ```tsx
141
- export const useContacts = createGlobalState({
142
- isLoading: true,
143
- entities: Map<number, Contact>,
144
- selected: Set<number>,
145
- });
146
-
147
- const useContactsArray = useContacts.createSelectorHook((state) => [...state.entities.values()], {
91
+ const [contacts] = useContacts((state) => state.entities.filter((item) => item.name.includes(filter)), {
92
+ dependencies: [filter],
148
93
  isEqualRoot: (a, b) => a.entities === b.entities,
149
94
  });
150
95
  ```
151
96
 
152
- Now inside your component just call the new hook
97
+ Unlike Redux, where only **root state changes trigger re-selection**, this approach ensures that **derived values recompute when dependencies change** while maintaining performance.
153
98
 
154
- **component.ts**
99
+ ---
155
100
 
156
- ```tsx
157
- const [filter, setFilter] = useState('');
158
-
159
- const [contacts] = useContactsArray((entities) => entities.name.includes(filter), {
160
- dependencies: [filter],
161
- });
162
- ```
101
+ ## πŸ”„ Reusing Selectors
163
102
 
164
- Or you can create another selectorHook from your **useContactsArray**
103
+ ### πŸ“Œ Creating a Selector
165
104
 
166
- ```ts
167
- const useContactsArray = useContacts.createSelectorHook((state) => [...state.entities.values()], {
168
- isEqualRoot: (a, b) => a.entities === b.entities,
169
- });
170
-
171
- const useContactsLength = useContactsArray.createSelectorHook((entities) => entities.length);
105
+ ```tsx
106
+ export const useContactsArray = useContacts.createSelectorHook((state) => state.entities);
107
+ export const useContactsCount = useContactsArray.createSelectorHook((entities) => entities.length);
172
108
  ```
173
109
 
174
- Or you can create a custom hook
110
+ ### πŸ“Œ Using Selectors in Components
175
111
 
176
112
  ```tsx
177
- const useFilteredContacts = (filter: string) => {
178
- const [contacts] = useContactsArray((entities) => entities.name.includes(filter), {
179
- dependencies: [filter],
180
- });
181
-
182
- return contacts;
183
- };
113
+ const [contacts] = useContactsArray();
114
+ const [count] = useContactsCount();
184
115
  ```
185
116
 
186
- To summarize
117
+ #### βœ… Selectors support inline selectors and dependencies
187
118
 
188
- ```tsx
189
- const [filter, setFilter] = useState('');
190
-
191
- const [contacts] = useContacts((state) => state.contacts.filter((contact) => contact.name.includes(filter)), {
192
- /**
193
- * You can use the `isEqualRoot` to validate if the values before the selector are equal.
194
- * This validation will run before `isEqual` and if the result is true the selector will not be recomputed.
195
- * If the result is true the re-render of the component will be prevented.
196
- */
197
- isEqualRoot: (r1, r2) => r1.filter === r2.filter,
198
-
199
- /**
200
- * You can use the `isEqual` to validate if the values after the selector are equal.
201
- * This validation will run after the selector computed a new value...
202
- * and if the result is true it will prevent the re-render of the component.
203
- */
204
- isEqual: (filter1, filter2) => filter1 === filter2,
205
-
206
- /**
207
- * You can use the `dependencies` array as with regular hooks to to force the recomputation of the selector.
208
- * Is important ot mention that changes in the dependencies will not trigger a re-render of the component...
209
- * Instead the recomputation of the selector will returned immediately.
210
- */
211
- dependencies: [filter],
212
- });
119
+ You can still **use dependencies** inside a selector hook:
213
120
 
214
- return (
215
- <ul>
216
- {contacts.map((contact) => (
217
- <li key={contact.id}>{contact.name}</li>
218
- ))}
219
- </ul>
121
+ ```tsx
122
+ const [filteredContacts] = useContactsArray(
123
+ (contacts) => contacts.filter((c) => c.name.includes(filter)),
124
+ [filter]
220
125
  );
221
126
  ```
222
127
 
223
- Btw, If you want to perform a shallow comparison between the previous and new values, you can use the **shallowCompare** function from the library.
224
-
225
- ```TSX
226
- ({
227
- /**
228
- * You can use the `shallowCompare` from the GlobalStore.utils to compare the values at first level.
229
- */
230
- isEqual: shallowCompare,
231
- })
232
- ```
233
-
234
- 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.
235
-
236
- More examples:
128
+ #### βœ… Selector hooks share the same state mutator
237
129
 
238
- ```ts
239
- const useFilter = useContacts.createSelectorHook(({ filter }) => filter);
130
+ The **stateMutator remains the same** across all derived selectors, meaning actions and setState functions stay consistent.
240
131
 
241
- const useContactsArray = useContacts.createSelectorHook(({ items }) => items);
242
-
243
- const useContactsLength = useContactsArray.createSelectorHook((items) => items.length);
132
+ ```tsx
133
+ const [actions1] = useContactsArray();
134
+ const [actions2] = useContactsCount();
244
135
 
245
- const useIsContactsEmpty = useContactsLength.createSelectorHook((length) => !length);
136
+ console.log(actions1 === actions2); // true
246
137
  ```
247
138
 
248
- 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.
249
-
250
- 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.
251
-
252
- # State actions
253
-
254
- 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**.
139
+ ---
255
140
 
256
- 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.
141
+ ## πŸŽ› State Actions: Controlling State Modifications
257
142
 
258
- ```ts
259
- import { createGlobalState } from 'react-native-global-state-hooks/createGlobalState';
143
+ Restrict **state modifications** by defining custom actions:
260
144
 
145
+ ```tsx
261
146
  export const useContacts = createGlobalState(
147
+ { filter: '', items: [] },
262
148
  {
263
- isLoading: true,
264
- filter: '',
265
- items: [] as Contact[],
266
- },
267
- {
268
- callbacks: {
269
- onInit: ({ setState }) => {
270
- // fetch contacts
271
- },
272
- },
273
- // this are the actions available for this state
274
149
  actions: {
150
+ async fetch() {
151
+ return async ({ setState }) => {
152
+ const items = await fetchItems();
153
+ setState({ items });
154
+ };
155
+ },
275
156
  setFilter(filter: string) {
276
157
  return ({ setState }) => {
277
- setState((state) => ({
278
- ...state,
279
- filter,
280
- }));
158
+ setState((state) => ({ ...state, filter }));
281
159
  };
282
160
  },
283
161
  },
@@ -285,129 +163,63 @@ export const useContacts = createGlobalState(
285
163
  );
286
164
  ```
287
165
 
288
- 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.
289
-
290
- Let's see how that will look now into our **FilterBar.tsx**
166
+ Now, instead of `setState`, the hook returns **actions**:
291
167
 
292
168
  ```tsx
293
- const [{ filter }, { setFilter }] = useFilter();
294
-
295
- return <TextInput onChangeText={setFilter} />;
296
- ```
297
-
298
- Yeah, that's it! All the **derived states** and **emitters** (we will talk about this later) will inherit the new actions interface.
299
-
300
- You can even **derive** from another **derived state**! Let's explore a few silly examples:
301
-
302
- ```ts
303
- const useFilter = createDerivate(useContacts, ({ filter }) => ({ filter }));
304
-
305
- const useFilterString = createDerivate(useFilter, { filter } => filter);
306
-
307
- const useContacts = createDerivate(useContacts, ({ items }) => items);
308
-
309
- const useContactsLength = createDerivate(useContacts, (items) => items.length);
310
-
311
- const useIsContactsEmpty = createDerivate(useContactsLength, (length) => !length);
169
+ const [filter, { setFilter }] = useContacts();
312
170
  ```
313
171
 
314
- 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.
172
+ ---
315
173
 
316
- # State Controls
174
+ ## 🌍 Accessing Global State Outside Components
317
175
 
318
- 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:
176
+ Use `stateControls()` to **retrieve or update state outside React components**:
319
177
 
320
178
  ```tsx
321
- useContacts.stateControls: () => [stateRetriever, stateMutator, Metadata];
322
-
323
- // example:
324
- const [getContacts, setContacts] = useContacts.stateControls();
325
-
326
- console.log(getContacts()); // prints the list of contacts
179
+ const [contactsRetriever, contactsApi] = useContacts.stateControls();
180
+ console.log(contactsRetriever()); // Retrieves the current state
327
181
  ```
328
182
 
329
- **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.
330
-
331
- 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.
332
-
333
- 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.
334
-
335
- 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.
183
+ #### βœ… Subscribe to changes
336
184
 
337
- ```ts
338
- /**
339
- * This not only allows you to retrieve the current value of the state...
340
- * but also enables you to subscribe to any changes in the state or a portion of it
341
- */
342
- const unsubscribe1 = contactsRetriever((state) => {
343
- console.log('state changed: ', state);
185
+ ```tsx
186
+ const unsubscribe = contactsRetriever((state) => {
187
+ console.log('State updated:', state);
344
188
  });
345
-
346
- const unsubscribe1 = contactsRetriever(
347
- (state) => state.isLoading,
348
- (isLoading) => {
349
- console.log('is loading changed', isLoading);
350
- }
351
- );
352
189
  ```
353
190
 
354
- That's great, isn't it? everything stays synchronized with the original state!!
355
-
356
- ## stateMutator
191
+ #### βœ… Subscriptions are great when one state depends on another.
357
192
 
358
- Let's add more actions to the state and explore how to use one action from inside another.
359
-
360
- Here's an example of adding multiple actions to the state and utilizing one action within another:
361
-
362
- ```ts
363
- import { createGlobalState } from 'react-hooks-global-states';
364
-
365
- export const useCount = createGlobalState(0, {
366
- actions: {
367
- log: (currentValue: string) => {
368
- return ({ getState }): void => {
369
- console.log(`Current Value: ${getState()}`);
370
- };
371
- },
372
-
373
- increase(value: number = 1) {
374
- return ({ getState, setState, actions }) => {
375
- const [, actions] = useCount.stateControls();
376
-
377
- setState((count) => count + value);
378
-
379
- actions.log(message);
380
- };
381
- },
382
-
383
- decrease(value: number = 1) {
384
- return ({ getState, setState, actions }) => {
385
- const [, actions] = useCount.stateControls();
386
-
387
- setState((count) => count - value);
388
-
389
- actions.log(message);
390
- };
193
+ ```tsx
194
+ const useSelectedContact = createGlobalState(null, {
195
+ callbacks: {
196
+ onInit: ({ setState, getState }) => {
197
+ contactsRetriever(
198
+ (state) => state.contacts,
199
+ (contacts) => {
200
+ if (!contacts.has(getState())) setState(null);
201
+ }
202
+ );
391
203
  },
392
204
  },
393
205
  });
394
206
  ```
395
207
 
396
- 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.
397
-
398
- # createContext
208
+ ---
399
209
 
400
- **createContext** 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.
210
+ ## 🎭 Using Context for Scoped State
401
211
 
402
- ## Creating a reusable context
212
+ - **Scoped State** – Context state is **isolated inside the provider**.
213
+ - **Same API** – Context supports **selectors, actions, and state controls**.
403
214
 
404
- Forget about the boilerplate of creating a context... with **createContext** it's straightforward and powerful. You can create a context and provider with one line of code.
215
+ ### πŸ“Œ Creating a Context
405
216
 
406
217
  ```tsx
407
- export const [useCounterContext, CounterProvider] = createContext(2);
218
+ import { createContext } from 'react-global-state-hooks/createContext';
219
+ export const [useCounterContext, CounterProvider] = createContext(0);
408
220
  ```
409
221
 
410
- Then just wrap the components you need with the provider:
222
+ Wrap your app:
411
223
 
412
224
  ```tsx
413
225
  <CounterProvider>
@@ -415,171 +227,108 @@ Then just wrap the components you need with the provider:
415
227
  </CounterProvider>
416
228
  ```
417
229
 
418
- And finally, access the context value with the **useCounterContext**, this function returns a **StateHook**.
419
-
420
- You can execute it immediately to subscribe to the state changes
230
+ Use the context state:
421
231
 
422
232
  ```tsx
423
- const MyComponentInsideTheProvider = () => {
424
- const [count] = useCounterContext()();
425
-
426
- return <span>{count}</span>;
427
- };
233
+ const [count] = useCounterContext();
428
234
  ```
429
235
 
430
- Or you can retrieve the **useCounterContext.stateControls();** to gain access to the getter and mutator without been affected by the changes on the state
431
-
432
- ```tsx
433
- const MyComponent = () => {
434
- // won't re-render if the counter changes
435
- const [getCount, setCount] = useCounterContext().stateControls();
236
+ ### πŸ“Œ Context Selectors
436
237
 
437
- return <button onClick={() => setCount((count) => count + 1)}>Increase</button>;
438
- };
439
- ```
238
+ Works **just like global state**, but within the provider.
440
239
 
441
- You'll still have selectors to extract just an specific portion of the state. If a selector is added the component only will change if that specific portion of the state changed.
240
+ ---
442
241
 
443
- ```tsx
444
- const MyComponent = () => {
445
- const [isEven, setCount] = useCounterContext()((count) => count % 2 === 0);
242
+ ## πŸ”₯ Observables: Watching State Changes
446
243
 
447
- useEffect(() => {
448
- // lets say that the initial state was *2* and we'll set it now to *4*
449
- setCount(4);
244
+ Observables **let you react to state changes** via subscriptions.
450
245
 
451
- // the component will not re-render cause 4 is also even
452
- }, []);
246
+ ### πŸ“Œ Creating an Observable
453
247
 
454
- return <>{isEven ? 'is even' : 'is odd'}</>;
455
- };
248
+ ```tsx
249
+ export const useCounter = createGlobalState(0);
250
+ export const counterLogs = useCounter.createObservable((count) => `Counter is at ${count}`);
456
251
  ```
457
252
 
458
- **createContext** also allows you to add custom actions to control the manipulation of the state inside the context
253
+ ### πŸ“Œ Subscribing to an Observable
459
254
 
460
255
  ```tsx
461
- import { createContext } from 'react-global-state-hooks/createContext';
462
-
463
- export const [useCounterContext, CounterProvider] = createStatefulContext(
464
- {
465
- count: 0,
466
- },
467
- {
468
- actions: {
469
- increase: (value: number = 1) => {
470
- return ({ setState }) => {
471
- setState((state) => ({
472
- ...state,
473
- count: state.count + value,
474
- }));
475
- };
476
- },
477
- decrease: (value: number = 1) => {
478
- return ({ setState }) => {
479
- setState((state) => ({
480
- ...state,
481
- count: state.count - value,
482
- }));
483
- };
484
- },
485
- },
486
- }
487
- );
256
+ const unsubscribe = counterLogs((message) => {
257
+ console.log(message);
258
+ });
488
259
  ```
489
260
 
490
- And just like with regular global hooks, now instead of a setState function, the hook will return the collection of actions
491
-
492
- Last but not least, you can still creating **selectorHooks** with the **createSelectorHook** function, this hooks will only work if the if they are contained in the scope of the provider.
261
+ ### πŸ“Œ Using Observables Inside Context
493
262
 
494
263
  ```tsx
495
- const useIsEven = useCounterContext.createSelectorHook((count) => count % 2 === 0);
264
+ export const [useStateControls, useObservableBuilder] = useCounterContext.stateControls();
265
+ const createObservable = useObservableBuilder();
266
+ useEffect(() => {
267
+ const unsubscribe = createObservable((count) => {
268
+ console.log(`Updated count: ${count}`);
269
+ });
270
+ return unsubscribe;
271
+ }, []);
496
272
  ```
497
273
 
498
- # Life cycle methods
499
-
500
- There are some lifecycle methods available for use with global hooks, let's review them callbacks prop:
501
-
502
- ```ts
503
- /**
504
- * @description callback function called when the store is initialized
505
- * @returns {void} result - void
506
- * */
507
- onInit?: ({
508
- /**
509
- * Set the metadata
510
- * @param {TMetadata} setter - The metadata or a function that will receive the metadata and return the new metadata
511
- * */
512
- setMetadata: MetadataSetter<TMetadata>;
513
-
514
- /**
515
- * Set the state
516
- * @param {TState} stateMutator - The state or a function that will receive the state and return the new state
517
- * @param {{ forceUpdate?: boolean }} options - Options
518
- * */
519
- setState: StateSetter<TState>;
520
-
521
- /**
522
- * Get the state
523
- * @returns {TState} result - The state
524
- * */
525
- getState: () => TState;
526
-
527
- /**
528
- * Get the metadata
529
- * @returns {TMetadata} result - The metadata
530
- * */
531
- getMetadata: () => TMetadata;
532
-
533
- /**
534
- * Actions of the hook if configuration was provided
535
- */
536
- actions: TActions;
537
- }: StateConfigCallbackParam<TState, TMetadata, TActions>) => void;
538
-
539
- /**
540
- * @description - callback function called every time the state is changed
541
- */
542
- onStateChanged?: (parameters: StoreTools<any, any> & StateChanges<unknown>) => void;
543
-
544
- /**
545
- * callback function called every time a component is subscribed to the store
546
- */
547
- onSubscribed?: (parameters: StateConfigCallbackParam<TState, TMetadata, TActions>) => void;
548
-
549
- /**
550
- * callback function called every time the state is about to change and it allows you to prevent the state change
551
- */
552
- computePreventStateChange?: (parameters: StoreTools<any, any> & StateChanges<unknown>) => boolean;
553
- ```
274
+ ---
275
+
276
+ ## βš–οΈ `createGlobalState` vs. `createContext`
277
+
278
+ | Feature | `createGlobalState` | `createContext` |
279
+ | ---------------------- | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
280
+ | **Scope** | Available globally across the entire app | Scoped to the Provider where it’s used |
281
+ | **How to Use** | `const useCount = createGlobalState(0)` | `const [useCountContext, Provider] = createContext(0)` |
282
+ | **createSelectorHook** | `useCount.createSelectorHook` | `useCountContext.createSelectorHook` |
283
+ | **inline selectors?** | βœ… Supported | βœ… Supported |
284
+ | **Custom Actions** | βœ… Supported | βœ… Supported |
285
+ | **Observables** | `useCount.createObservable` | `const [, useObservableBuilder] = useCountContext.stateControls()` |
286
+ | **State Controls** | `useCount.stateControls()` | `const [useStateControls] = useCountContext.stateControls()` |
287
+ | **Best For** | Global app state (auth, settings, cache) | Scoped module state, reusable component state, or state shared between child components without being fully global |
554
288
 
555
- You can pass this callbacks between on the second parameter of the builders like **createGlobalState**
289
+ ## πŸ”„ Lifecycle Methods
556
290
 
557
- ```ts
291
+ Global state hooks support lifecycle callbacks for additional control.
292
+
293
+ ```tsx
558
294
  const useData = createGlobalState(
559
295
  { value: 1 },
560
296
  {
561
- metadata: {
562
- someExtraInformation: 'someExtraInformation',
563
- },
564
297
  callbacks: {
565
- // onSubscribed: (StateConfigCallbackParam) => {},
566
- // onInit // etc
298
+ onInit: ({ setState }) => {
299
+ console.log('Store initialized');
300
+ },
301
+ onStateChanged: ({ state, previousState }) => {
302
+ console.log('State changed:', previousState, 'β†’', state);
303
+ },
567
304
  computePreventStateChange: ({ state, previousState }) => {
568
- const prevent = isEqual(state, previousState);
569
-
570
- return prevent;
305
+ return state.value === previousState.value;
571
306
  },
572
307
  },
573
308
  }
574
309
  );
575
310
  ```
576
311
 
577
- Finally, if you have a very specific necessity but still want to use the global hooks, you can extend the **GlobalStoreAbstract** class. This will give you even more control over the state and the lifecycle of the global state.
312
+ Use **`onInit`** for setup, **`onStateChanged`** to listen to updates, and **`computePreventStateChange`** to prevent unnecessary updates.
578
313
 
579
- Let's see an example again with the **asyncStorage** custom global hook but with the abstract class.
314
+ ## Metadata
580
315
 
581
- ```ts
582
- extends GlobalStoreAbstract<TState, TMetadata, TStateSetter>
316
+ There is a possibility to add non reactive information in the global state:
317
+
318
+ ```tsx
319
+ const useCount = createGlobalState(0, { metadata: { renders: 0 } });
583
320
  ```
584
321
 
585
- # That's it for now!! hope you enjoy coding!!
322
+ How to use it?
323
+
324
+ ```tsx
325
+ const [count, , metadata] = useCount();
326
+
327
+ metadata.renders += 1;
328
+ ```
329
+
330
+ ## 🎯 Ready to Try It?
331
+
332
+ πŸ“¦ **NPM Package:** [react-hooks-global-states](https://www.npmjs.com/package/react-hooks-global-states)
333
+
334
+ πŸš€ Simplify your **global state management** in React & React Native today! πŸš€
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-global-state-hooks",
3
- "version": "8.0.12",
3
+ "version": "8.0.14",
4
4
  "description": "This is a package to easily handling global-state across your react components No-redux, No-context.",
5
5
  "main": "./bundle.js",
6
6
  "types": "./index.d.ts",