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.
- package/README.md +175 -426
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,94 +1,70 @@
|
|
|
1
|
-
# react-global-
|
|
1
|
+
# react-hooks-global-states π
|
|
2
2
|
|
|
3
3
|

|
|
4
4
|
|
|
5
|
-
|
|
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
|
-
|
|
7
|
+
---
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
## π Explore More
|
|
10
10
|
|
|
11
|
-
|
|
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
|
-
|
|
17
|
+
Works seamlessly with **React & React Native**:
|
|
14
18
|
|
|
15
|
-
|
|
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
|
-
|
|
22
|
+
---
|
|
18
23
|
|
|
19
|
-
|
|
24
|
+
## ποΈ Persisting State with LocalStorage
|
|
20
25
|
|
|
21
|
-
|
|
26
|
+
To persist the global state using **LocalStorage**, simply add the `localStorage` option:
|
|
22
27
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
```tsx
|
|
29
|
+
const useContacts = createGlobalState(new Map(), {
|
|
30
|
+
localStorage: {
|
|
31
|
+
key: 'contacts',
|
|
32
|
+
},
|
|
33
|
+
});
|
|
29
34
|
```
|
|
30
35
|
|
|
31
|
-
|
|
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
|
-
|
|
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
|
-
|
|
45
|
+
Now, use it inside a component:
|
|
42
46
|
|
|
43
|
-
```
|
|
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
|
-
|
|
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
|
-
|
|
54
|
+
---
|
|
70
55
|
|
|
71
|
-
|
|
56
|
+
## π― Selectors: Subscribing to Specific State Changes
|
|
72
57
|
|
|
73
|
-
|
|
58
|
+
For **complex state objects**, you can subscribe to specific properties instead of the entire state:
|
|
74
59
|
|
|
75
|
-
```
|
|
76
|
-
|
|
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
|
-
|
|
64
|
+
To access only the `entities` property:
|
|
86
65
|
|
|
87
66
|
```tsx
|
|
88
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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) =>
|
|
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
|
-
|
|
88
|
+
Alternatively, you can pass dependencies inside an **options object**:
|
|
139
89
|
|
|
140
90
|
```tsx
|
|
141
|
-
|
|
142
|
-
|
|
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
|
-
|
|
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
|
-
|
|
99
|
+
---
|
|
155
100
|
|
|
156
|
-
|
|
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
|
-
|
|
103
|
+
### π Creating a Selector
|
|
165
104
|
|
|
166
|
-
```
|
|
167
|
-
const useContactsArray = useContacts.createSelectorHook((state) =>
|
|
168
|
-
|
|
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
|
-
|
|
110
|
+
### π Using Selectors in Components
|
|
175
111
|
|
|
176
112
|
```tsx
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
dependencies: [filter],
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
return contacts;
|
|
183
|
-
};
|
|
113
|
+
const [contacts] = useContactsArray();
|
|
114
|
+
const [count] = useContactsCount();
|
|
184
115
|
```
|
|
185
116
|
|
|
186
|
-
|
|
117
|
+
#### β
Selectors support inline selectors and dependencies
|
|
187
118
|
|
|
188
|
-
|
|
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
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
242
|
-
|
|
243
|
-
const
|
|
132
|
+
```tsx
|
|
133
|
+
const [actions1] = useContactsArray();
|
|
134
|
+
const [actions2] = useContactsCount();
|
|
244
135
|
|
|
245
|
-
|
|
136
|
+
console.log(actions1 === actions2); // true
|
|
246
137
|
```
|
|
247
138
|
|
|
248
|
-
|
|
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
|
-
|
|
141
|
+
## π State Actions: Controlling State Modifications
|
|
257
142
|
|
|
258
|
-
|
|
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
|
-
|
|
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 [
|
|
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
|
-
|
|
172
|
+
---
|
|
315
173
|
|
|
316
|
-
|
|
174
|
+
## π Accessing Global State Outside Components
|
|
317
175
|
|
|
318
|
-
|
|
176
|
+
Use `stateControls()` to **retrieve or update state outside React components**:
|
|
319
177
|
|
|
320
178
|
```tsx
|
|
321
|
-
useContacts.stateControls
|
|
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
|
-
|
|
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
|
-
```
|
|
338
|
-
|
|
339
|
-
|
|
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
|
-
|
|
355
|
-
|
|
356
|
-
## stateMutator
|
|
191
|
+
#### β
Subscriptions are great when one state depends on another.
|
|
357
192
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
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
|
-
|
|
397
|
-
|
|
398
|
-
# createContext
|
|
208
|
+
---
|
|
399
209
|
|
|
400
|
-
|
|
210
|
+
## π Using Context for Scoped State
|
|
401
211
|
|
|
402
|
-
|
|
212
|
+
- **Scoped State** β Context state is **isolated inside the provider**.
|
|
213
|
+
- **Same API** β Context supports **selectors, actions, and state controls**.
|
|
403
214
|
|
|
404
|
-
|
|
215
|
+
### π Creating a Context
|
|
405
216
|
|
|
406
217
|
```tsx
|
|
407
|
-
|
|
218
|
+
import { createContext } from 'react-global-state-hooks/createContext';
|
|
219
|
+
export const [useCounterContext, CounterProvider] = createContext(0);
|
|
408
220
|
```
|
|
409
221
|
|
|
410
|
-
|
|
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
|
-
|
|
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
|
|
424
|
-
const [count] = useCounterContext()();
|
|
425
|
-
|
|
426
|
-
return <span>{count}</span>;
|
|
427
|
-
};
|
|
233
|
+
const [count] = useCounterContext();
|
|
428
234
|
```
|
|
429
235
|
|
|
430
|
-
|
|
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
|
-
|
|
438
|
-
};
|
|
439
|
-
```
|
|
238
|
+
Works **just like global state**, but within the provider.
|
|
440
239
|
|
|
441
|
-
|
|
240
|
+
---
|
|
442
241
|
|
|
443
|
-
|
|
444
|
-
const MyComponent = () => {
|
|
445
|
-
const [isEven, setCount] = useCounterContext()((count) => count % 2 === 0);
|
|
242
|
+
## π₯ Observables: Watching State Changes
|
|
446
243
|
|
|
447
|
-
|
|
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
|
-
|
|
452
|
-
}, []);
|
|
246
|
+
### π Creating an Observable
|
|
453
247
|
|
|
454
|
-
|
|
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
|
-
|
|
253
|
+
### π Subscribing to an Observable
|
|
459
254
|
|
|
460
255
|
```tsx
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
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
|
-
|
|
289
|
+
## π Lifecycle Methods
|
|
556
290
|
|
|
557
|
-
|
|
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
|
-
|
|
566
|
-
|
|
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
|
-
|
|
569
|
-
|
|
570
|
-
return prevent;
|
|
305
|
+
return state.value === previousState.value;
|
|
571
306
|
},
|
|
572
307
|
},
|
|
573
308
|
}
|
|
574
309
|
);
|
|
575
310
|
```
|
|
576
311
|
|
|
577
|
-
|
|
312
|
+
Use **`onInit`** for setup, **`onStateChanged`** to listen to updates, and **`computePreventStateChange`** to prevent unnecessary updates.
|
|
578
313
|
|
|
579
|
-
|
|
314
|
+
## Metadata
|
|
580
315
|
|
|
581
|
-
|
|
582
|
-
|
|
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
|
-
|
|
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