react-hooks-global-states 7.0.10 β 7.0.12
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 +167 -381
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -2,65 +2,57 @@
|
|
|
2
2
|
|
|
3
3
|

|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Effortless **global state management** for `React` & `React Native` & `Preact`! π 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
|
+
## π Creating a Global State
|
|
20
25
|
|
|
21
|
-
|
|
26
|
+
Define a **global state** in **one line**:
|
|
22
27
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
```ts
|
|
28
|
+
```tsx
|
|
26
29
|
import { createGlobalState } from 'react-hooks-global-states/createGlobalState';
|
|
27
|
-
|
|
28
30
|
export const useCount = createGlobalState(0);
|
|
29
31
|
```
|
|
30
32
|
|
|
31
|
-
|
|
33
|
+
Now, use it inside a component:
|
|
32
34
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
```ts
|
|
35
|
+
```tsx
|
|
36
36
|
const [count, setCount] = useCount();
|
|
37
|
-
|
|
38
37
|
return <Button onClick={() => setCount((count) => count + 1)}>{count}</Button>;
|
|
39
38
|
```
|
|
40
39
|
|
|
41
|
-
|
|
40
|
+
Works just like **useState**, but the **state is shared globally**! π
|
|
42
41
|
|
|
43
|
-
|
|
42
|
+
---
|
|
44
43
|
|
|
45
|
-
|
|
44
|
+
## π― Selectors: Subscribing to Specific State Changes
|
|
46
45
|
|
|
47
|
-
|
|
48
|
-
import { createGlobalState } from 'react-hooks-global-states/createGlobalState';
|
|
46
|
+
For **complex state objects**, you can subscribe to specific properties instead of the entire state:
|
|
49
47
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
entities: Contact[],
|
|
53
|
-
selected: Set<number>,
|
|
54
|
-
});
|
|
48
|
+
```tsx
|
|
49
|
+
export const useContacts = createGlobalState({ entities: [], selected: new Set<number>() });
|
|
55
50
|
```
|
|
56
51
|
|
|
57
|
-
|
|
52
|
+
To access only the `entities` property:
|
|
58
53
|
|
|
59
54
|
```tsx
|
|
60
|
-
|
|
61
|
-
// and the component will only re-render if the property **entities** changes on the global state
|
|
62
|
-
const [contacts] = useContacts((state) => state.entities]);
|
|
63
|
-
|
|
55
|
+
const [contacts] = useContacts((state) => state.entities);
|
|
64
56
|
return (
|
|
65
57
|
<ul>
|
|
66
58
|
{contacts.map((contact) => (
|
|
@@ -70,297 +62,152 @@ return (
|
|
|
70
62
|
);
|
|
71
63
|
```
|
|
72
64
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
```tsx
|
|
76
|
-
export const useContacts = createGlobalState({
|
|
77
|
-
isLoading: true,
|
|
78
|
-
entities: Map<number, Contact>,
|
|
79
|
-
selected: Set<number>,
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
// The selector is simply a standard selector used to extract the values from the map.
|
|
83
|
-
|
|
84
|
-
const [contacts] = useContacts((state) => [...state.entities.values()], {
|
|
85
|
-
// The isEqualRoots function allows you to create your own validation logic for determining when to recompute the selector.
|
|
86
|
-
isEqualRoot: (a, b) => a.entities === b.entities,
|
|
87
|
-
});
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
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?
|
|
65
|
+
### π Using Dependencies in Selectors
|
|
91
66
|
|
|
92
|
-
**
|
|
67
|
+
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:
|
|
93
68
|
|
|
94
69
|
```tsx
|
|
95
|
-
const [filter, setFilter] = useState('');
|
|
96
|
-
|
|
97
70
|
const [contacts] = useContacts(
|
|
98
|
-
(state) =>
|
|
99
|
-
|
|
100
|
-
isEqualRoot: (a, b) => a.entities === b.entities,
|
|
101
|
-
/**
|
|
102
|
-
* Easy to understand, right? With the dependencies prop, you can,
|
|
103
|
-
* just like with any other hook, provide a collection of values that will be compared during each render cycle
|
|
104
|
-
* to determine if the selector should be recomputed.*/
|
|
105
|
-
dependencies: [filter],
|
|
106
|
-
}
|
|
71
|
+
(state) => state.entities.filter((item) => item.name.includes(filter)),
|
|
72
|
+
[filter]
|
|
107
73
|
);
|
|
108
74
|
```
|
|
109
75
|
|
|
110
|
-
|
|
76
|
+
Alternatively, you can pass dependencies inside an **options object**:
|
|
111
77
|
|
|
112
78
|
```tsx
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
entities: Map<number, Contact>,
|
|
116
|
-
selected: Set<number>,
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
const useContactsArray = useContacts.createSelectorHook((state) => [...state.entities.values()], {
|
|
79
|
+
const [contacts] = useContacts((state) => state.entities.filter((item) => item.name.includes(filter)), {
|
|
80
|
+
dependencies: [filter],
|
|
120
81
|
isEqualRoot: (a, b) => a.entities === b.entities,
|
|
121
82
|
});
|
|
122
83
|
```
|
|
123
84
|
|
|
124
|
-
|
|
85
|
+
Unlike Redux, where only **root state changes trigger re-selection**, this approach ensures that **derived values recompute when dependencies change** while maintaining performance.
|
|
125
86
|
|
|
126
|
-
|
|
87
|
+
---
|
|
127
88
|
|
|
128
|
-
|
|
129
|
-
const [filter, setFilter] = useState('');
|
|
89
|
+
## π Reusing Selectors
|
|
130
90
|
|
|
131
|
-
|
|
132
|
-
dependencies: [filter],
|
|
133
|
-
});
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
Or you can create another selectorHook from your **useContactsArray**
|
|
91
|
+
### π Creating a Selector
|
|
137
92
|
|
|
138
|
-
```
|
|
139
|
-
const useContactsArray = useContacts.createSelectorHook((state) =>
|
|
140
|
-
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
const useContactsLength = useContactsArray.createSelectorHook((entities) => entities.length);
|
|
93
|
+
```tsx
|
|
94
|
+
export const useContactsArray = useContacts.createSelectorHook((state) => state.entities);
|
|
95
|
+
export const useContactsCount = useContactsArray.createSelectorHook((entities) => entities.length);
|
|
144
96
|
```
|
|
145
97
|
|
|
146
|
-
|
|
98
|
+
### π Using Selectors in Components
|
|
147
99
|
|
|
148
100
|
```tsx
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
dependencies: [filter],
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
return contacts;
|
|
155
|
-
};
|
|
101
|
+
const [contacts] = useContactsArray();
|
|
102
|
+
const [count] = useContactsCount();
|
|
156
103
|
```
|
|
157
104
|
|
|
158
|
-
|
|
105
|
+
#### β
Selectors support inline selectors and dependencies
|
|
159
106
|
|
|
160
|
-
|
|
161
|
-
const [filter, setFilter] = useState('');
|
|
162
|
-
|
|
163
|
-
const [contacts] = useContacts((state) => state.contacts.filter((contact) => contact.name.includes(filter)), {
|
|
164
|
-
/**
|
|
165
|
-
* You can use the `isEqualRoot` to validate if the values before the selector are equal.
|
|
166
|
-
* This validation will run before `isEqual` and if the result is true the selector will not be recomputed.
|
|
167
|
-
* If the result is true the re-render of the component will be prevented.
|
|
168
|
-
*/
|
|
169
|
-
isEqualRoot: (r1, r2) => r1.filter === r2.filter,
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* You can use the `isEqual` to validate if the values after the selector are equal.
|
|
173
|
-
* This validation will run after the selector computed a new value...
|
|
174
|
-
* and if the result is true it will prevent the re-render of the component.
|
|
175
|
-
*/
|
|
176
|
-
isEqual: (filter1, filter2) => filter1 === filter2,
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* You can use the `dependencies` array as with regular hooks to to force the recomputation of the selector.
|
|
180
|
-
* Is important ot mention that changes in the dependencies will not trigger a re-render of the component...
|
|
181
|
-
* Instead the recomputation of the selector will returned immediately.
|
|
182
|
-
*/
|
|
183
|
-
dependencies: [filter],
|
|
184
|
-
});
|
|
107
|
+
You can still **use dependencies** inside a selector hook:
|
|
185
108
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
))}
|
|
191
|
-
</ul>
|
|
109
|
+
```tsx
|
|
110
|
+
const [filteredContacts] = useContactsArray(
|
|
111
|
+
(contacts) => contacts.filter((c) => c.name.includes(filter)),
|
|
112
|
+
[filter]
|
|
192
113
|
);
|
|
193
114
|
```
|
|
194
115
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
```TSX
|
|
198
|
-
({
|
|
199
|
-
/**
|
|
200
|
-
* You can use the `shallowCompare` from the GlobalStore.utils to compare the values at first level.
|
|
201
|
-
*/
|
|
202
|
-
isEqual: shallowCompare,
|
|
203
|
-
})
|
|
204
|
-
```
|
|
205
|
-
|
|
206
|
-
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.
|
|
207
|
-
|
|
208
|
-
More examples:
|
|
209
|
-
|
|
210
|
-
```ts
|
|
211
|
-
const useFilter = useContacts.createSelectorHook(({ filter }) => filter);
|
|
116
|
+
#### β
Selector hooks share the same state mutator
|
|
212
117
|
|
|
213
|
-
|
|
118
|
+
The **stateMutator remains the same** across all derived selectors, meaning actions and setState functions stay consistent.
|
|
214
119
|
|
|
215
|
-
|
|
120
|
+
```tsx
|
|
121
|
+
const [actions1] = useContactsArray();
|
|
122
|
+
const [actions2] = useContactsCount();
|
|
216
123
|
|
|
217
|
-
|
|
124
|
+
console.log(actions1 === actions2); // true
|
|
218
125
|
```
|
|
219
126
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
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.
|
|
223
|
-
|
|
224
|
-
# State actions
|
|
225
|
-
|
|
226
|
-
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**.
|
|
127
|
+
---
|
|
227
128
|
|
|
228
|
-
|
|
129
|
+
## π State Actions: Controlling State Modifications
|
|
229
130
|
|
|
230
|
-
|
|
231
|
-
import { createGlobalState } from 'react-hooks-global-states/createGlobalState';
|
|
131
|
+
Restrict **state modifications** by defining custom actions:
|
|
232
132
|
|
|
133
|
+
```tsx
|
|
233
134
|
export const useContacts = createGlobalState(
|
|
135
|
+
{ filter: '', items: [] },
|
|
234
136
|
{
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
onInit: ({ setState }) => {
|
|
242
|
-
// fetch contacts
|
|
137
|
+
actions: {
|
|
138
|
+
async fetch() {
|
|
139
|
+
return async ({ setState }) => {
|
|
140
|
+
const items = await fetchItems();
|
|
141
|
+
setState({ items });
|
|
142
|
+
};
|
|
243
143
|
},
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
...state,
|
|
249
|
-
filter,
|
|
250
|
-
}));
|
|
251
|
-
};
|
|
252
|
-
},
|
|
144
|
+
setFilter(filter: string) {
|
|
145
|
+
return ({ setState }) => {
|
|
146
|
+
setState((state) => ({ ...state, filter }));
|
|
147
|
+
};
|
|
253
148
|
},
|
|
254
149
|
},
|
|
255
150
|
}
|
|
256
151
|
);
|
|
257
152
|
```
|
|
258
153
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
Let's see how that will look now into our **FilterBar.tsx**
|
|
154
|
+
Now, instead of `setState`, the hook returns **actions**:
|
|
262
155
|
|
|
263
156
|
```tsx
|
|
264
|
-
const [filter, { setFilter }] =
|
|
265
|
-
|
|
266
|
-
return <TextInput onChangeText={setFilter} />;
|
|
157
|
+
const [filter, { setFilter }] = useContacts();
|
|
267
158
|
```
|
|
268
159
|
|
|
269
|
-
|
|
160
|
+
---
|
|
270
161
|
|
|
271
|
-
|
|
162
|
+
## π Accessing Global State Outside Components
|
|
272
163
|
|
|
273
|
-
|
|
164
|
+
Use `stateControls()` to **retrieve or update state outside React components**:
|
|
274
165
|
|
|
275
166
|
```tsx
|
|
276
|
-
useContacts.stateControls
|
|
277
|
-
|
|
278
|
-
// example:
|
|
279
|
-
const [getContacts, setContacts] = useContacts.stateControls();
|
|
280
|
-
|
|
281
|
-
console.log(getContacts()); // prints the list of contacts
|
|
167
|
+
const [contactsRetriever, contactsApi] = useContacts.stateControls();
|
|
168
|
+
console.log(contactsRetriever()); // Retrieves the current state
|
|
282
169
|
```
|
|
283
170
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
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.
|
|
287
|
-
|
|
288
|
-
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.
|
|
289
|
-
|
|
290
|
-
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.
|
|
171
|
+
#### β
Subscribe to changes
|
|
291
172
|
|
|
292
|
-
```
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
* but also enables you to subscribe to any changes in the state or a portion of it
|
|
296
|
-
*/
|
|
297
|
-
const unsubscribe1 = contactsRetriever((state) => {
|
|
298
|
-
console.log('state changed: ', state);
|
|
173
|
+
```tsx
|
|
174
|
+
const unsubscribe = contactsRetriever((state) => {
|
|
175
|
+
console.log('State updated:', state);
|
|
299
176
|
});
|
|
300
|
-
|
|
301
|
-
const unsubscribe2 = contactsRetriever(
|
|
302
|
-
(state) => state.isLoading,
|
|
303
|
-
(isLoading) => {
|
|
304
|
-
console.log('is loading changed', isLoading);
|
|
305
|
-
}
|
|
306
|
-
);
|
|
307
177
|
```
|
|
308
178
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
## stateMutator
|
|
312
|
-
|
|
313
|
-
Let's add more actions to the state and explore how to use one action from inside another.
|
|
314
|
-
|
|
315
|
-
Here's an example of adding multiple actions to the state and utilizing one action within another:
|
|
316
|
-
|
|
317
|
-
```ts
|
|
318
|
-
import { createGlobalState } from 'react-hooks-global-states/createGlobalState';
|
|
319
|
-
|
|
320
|
-
export const useCount = createGlobalState(0, {
|
|
321
|
-
actions: {
|
|
322
|
-
log: (currentValue: string) => {
|
|
323
|
-
return ({ getState }): void => {
|
|
324
|
-
console.log(`Current Value: ${getState()}`);
|
|
325
|
-
};
|
|
326
|
-
},
|
|
327
|
-
|
|
328
|
-
increase(value: number = 1) {
|
|
329
|
-
return ({ getState, setState, actions }) => {
|
|
330
|
-
setState((count) => count + value);
|
|
179
|
+
#### β
Subscriptions are great when one state depends on another.
|
|
331
180
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
181
|
+
```tsx
|
|
182
|
+
const useSelectedContact = createGlobalState(null, {
|
|
183
|
+
callbacks: {
|
|
184
|
+
onInit: ({ setState, getState }) => {
|
|
185
|
+
contactsRetriever(
|
|
186
|
+
(state) => state.contacts,
|
|
187
|
+
(contacts) => {
|
|
188
|
+
if (!contacts.has(getState())) setState(null);
|
|
189
|
+
}
|
|
190
|
+
);
|
|
342
191
|
},
|
|
343
192
|
},
|
|
344
193
|
});
|
|
345
194
|
```
|
|
346
195
|
|
|
347
|
-
|
|
196
|
+
---
|
|
348
197
|
|
|
349
|
-
|
|
198
|
+
## π Using Context for Scoped State
|
|
350
199
|
|
|
351
|
-
**
|
|
200
|
+
- **Scoped State** β Context state is **isolated inside the provider**.
|
|
201
|
+
- **Same API** β Context supports **selectors, actions, and state controls**.
|
|
352
202
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
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.
|
|
203
|
+
### π Creating a Context
|
|
356
204
|
|
|
357
205
|
```tsx
|
|
358
|
-
import {
|
|
359
|
-
|
|
360
|
-
export const [useCounterContext, CounterProvider] = createContext(2);
|
|
206
|
+
import { createContext } from 'react-global-state-hooks/createContext';
|
|
207
|
+
export const [useCounterContext, CounterProvider] = createContext(0);
|
|
361
208
|
```
|
|
362
209
|
|
|
363
|
-
|
|
210
|
+
Wrap your app:
|
|
364
211
|
|
|
365
212
|
```tsx
|
|
366
213
|
<CounterProvider>
|
|
@@ -368,169 +215,108 @@ Then just wrap the components you need with the provider:
|
|
|
368
215
|
</CounterProvider>
|
|
369
216
|
```
|
|
370
217
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
You can execute it immediately to subscribe to the state changes
|
|
218
|
+
Use the context state:
|
|
374
219
|
|
|
375
220
|
```tsx
|
|
376
|
-
const
|
|
377
|
-
const [count] = useCounterContext()();
|
|
378
|
-
|
|
379
|
-
return <>{count}</>;
|
|
380
|
-
};
|
|
221
|
+
const [count] = useCounterContext();
|
|
381
222
|
```
|
|
382
223
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
```tsx
|
|
386
|
-
const MyComponent = () => {
|
|
387
|
-
// won't re-render if the counter changes
|
|
388
|
-
const [getCount, setCount] = useCounterContext().stateControls();
|
|
224
|
+
### π Context Selectors
|
|
389
225
|
|
|
390
|
-
|
|
391
|
-
};
|
|
392
|
-
```
|
|
226
|
+
Works **just like global state**, but within the provider.
|
|
393
227
|
|
|
394
|
-
|
|
228
|
+
---
|
|
395
229
|
|
|
396
|
-
|
|
397
|
-
const MyComponent = () => {
|
|
398
|
-
const [isEven, setCount] = useCounterContext()((count) => count % 2 === 0);
|
|
230
|
+
## π₯ Observables: Watching State Changes
|
|
399
231
|
|
|
400
|
-
|
|
401
|
-
// lets say that the initial state was *2* and we'll set it now to *4*
|
|
402
|
-
setCount(4);
|
|
232
|
+
Observables **let you react to state changes** via subscriptions.
|
|
403
233
|
|
|
404
|
-
|
|
405
|
-
}, []);
|
|
234
|
+
### π Creating an Observable
|
|
406
235
|
|
|
407
|
-
|
|
408
|
-
|
|
236
|
+
```tsx
|
|
237
|
+
export const useCounter = createGlobalState(0);
|
|
238
|
+
export const counterLogs = useCounter.createObservable((count) => `Counter is at ${count}`);
|
|
409
239
|
```
|
|
410
240
|
|
|
411
|
-
|
|
241
|
+
### π Subscribing to an Observable
|
|
412
242
|
|
|
413
243
|
```tsx
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
{ count: 0 },
|
|
418
|
-
{
|
|
419
|
-
actions: {
|
|
420
|
-
increase: (value: number = 1) => {
|
|
421
|
-
return ({ setState }) => {
|
|
422
|
-
setState((state) => ({
|
|
423
|
-
...state,
|
|
424
|
-
count: state.count + value,
|
|
425
|
-
}));
|
|
426
|
-
};
|
|
427
|
-
},
|
|
428
|
-
decrease: (value: number = 1) => {
|
|
429
|
-
return ({ setState }) => {
|
|
430
|
-
setState((state) => ({
|
|
431
|
-
...state,
|
|
432
|
-
count: state.count - value,
|
|
433
|
-
}));
|
|
434
|
-
};
|
|
435
|
-
},
|
|
436
|
-
},
|
|
437
|
-
}
|
|
438
|
-
);
|
|
244
|
+
const unsubscribe = counterLogs((message) => {
|
|
245
|
+
console.log(message);
|
|
246
|
+
});
|
|
439
247
|
```
|
|
440
248
|
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
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.
|
|
249
|
+
### π Using Observables Inside Context
|
|
444
250
|
|
|
445
251
|
```tsx
|
|
446
|
-
const
|
|
252
|
+
export const [useStateControls, useObservableBuilder] = useCounterContext.stateControls();
|
|
253
|
+
const createObservable = useObservableBuilder();
|
|
254
|
+
useEffect(() => {
|
|
255
|
+
const unsubscribe = createObservable((count) => {
|
|
256
|
+
console.log(`Updated count: ${count}`);
|
|
257
|
+
});
|
|
258
|
+
return unsubscribe;
|
|
259
|
+
}, []);
|
|
447
260
|
```
|
|
448
261
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
setMetadata: MetadataSetter<TMetadata>;
|
|
464
|
-
|
|
465
|
-
/**
|
|
466
|
-
* Set the state
|
|
467
|
-
* @param {TState} setter - The state or a function that will receive the state and return the new state
|
|
468
|
-
* @param {{ forceUpdate?: boolean }} options - Options
|
|
469
|
-
* */
|
|
470
|
-
setState: StateSetter<TState>;
|
|
471
|
-
|
|
472
|
-
/**
|
|
473
|
-
* Get the state
|
|
474
|
-
* @returns {TState} result - The state
|
|
475
|
-
* */
|
|
476
|
-
getState: () => TState;
|
|
477
|
-
|
|
478
|
-
/**
|
|
479
|
-
* Get the metadata
|
|
480
|
-
* @returns {TMetadata} result - The metadata
|
|
481
|
-
* */
|
|
482
|
-
getMetadata: () => TMetadata;
|
|
483
|
-
|
|
484
|
-
/**
|
|
485
|
-
* Actions of the hook if configuration was provided
|
|
486
|
-
*/
|
|
487
|
-
actions: TActions;
|
|
488
|
-
}: StateConfigCallbackParam<TState, TMetadata, TActions>) => void;
|
|
489
|
-
|
|
490
|
-
/**
|
|
491
|
-
* @description - callback function called every time the state is changed
|
|
492
|
-
*/
|
|
493
|
-
onStateChanged?: (parameters: StateChangesParam<TState, TMetadata, TActions>) => void;
|
|
494
|
-
|
|
495
|
-
/**
|
|
496
|
-
* callback function called every time a component is subscribed to the store
|
|
497
|
-
*/
|
|
498
|
-
onSubscribed?: (parameters: StateConfigCallbackParam<TState, TMetadata, TActions>) => void;
|
|
499
|
-
|
|
500
|
-
/**
|
|
501
|
-
* callback function called every time the state is about to change and it allows you to prevent the state change
|
|
502
|
-
*/
|
|
503
|
-
computePreventStateChange?: (parameters: StateChangesParam<TState, TMetadata, TActions>) => boolean;
|
|
504
|
-
```
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
## βοΈ `createGlobalState` vs. `createContext`
|
|
265
|
+
|
|
266
|
+
| Feature | `createGlobalState` | `createContext` |
|
|
267
|
+
| ---------------------- | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
|
|
268
|
+
| **Scope** | Available globally across the entire app | Scoped to the Provider where itβs used |
|
|
269
|
+
| **How to Use** | `const useCount = createGlobalState(0)` | `const [useCountContext, Provider] = createContext(0)` |
|
|
270
|
+
| **createSelectorHook** | `useCount.createSelectorHook` | `useCountContext.createSelectorHook` |
|
|
271
|
+
| **inline selectors?** | β
Supported | β
Supported |
|
|
272
|
+
| **Custom Actions** | β
Supported | β
Supported |
|
|
273
|
+
| **Observables** | `useCount.createObservable` | `const [, useObservableBuilder] = useCountContext.stateControls()` |
|
|
274
|
+
| **State Controls** | `useCount.stateControls()` | `const [useStateControls] = useCountContext.stateControls()` |
|
|
275
|
+
| **Best For** | Global app state (auth, settings, cache) | Scoped module state, reusable component state, or state shared between child components without being fully global |
|
|
505
276
|
|
|
506
|
-
|
|
277
|
+
## π Lifecycle Methods
|
|
507
278
|
|
|
508
|
-
|
|
279
|
+
Global state hooks support lifecycle callbacks for additional control.
|
|
280
|
+
|
|
281
|
+
```tsx
|
|
509
282
|
const useData = createGlobalState(
|
|
510
283
|
{ value: 1 },
|
|
511
284
|
{
|
|
512
|
-
metadata: {
|
|
513
|
-
someExtraInformation: 'someExtraInformation',
|
|
514
|
-
},
|
|
515
285
|
callbacks: {
|
|
516
|
-
|
|
517
|
-
|
|
286
|
+
onInit: ({ setState }) => {
|
|
287
|
+
console.log('Store initialized');
|
|
288
|
+
},
|
|
289
|
+
onStateChanged: ({ state, previousState }) => {
|
|
290
|
+
console.log('State changed:', previousState, 'β', state);
|
|
291
|
+
},
|
|
518
292
|
computePreventStateChange: ({ state, previousState }) => {
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
return prevent;
|
|
293
|
+
return state.value === previousState.value;
|
|
522
294
|
},
|
|
523
295
|
},
|
|
524
296
|
}
|
|
525
297
|
);
|
|
526
298
|
```
|
|
527
299
|
|
|
528
|
-
|
|
300
|
+
Use **`onInit`** for setup, **`onStateChanged`** to listen to updates, and **`computePreventStateChange`** to prevent unnecessary updates.
|
|
301
|
+
|
|
302
|
+
## Metadata
|
|
303
|
+
|
|
304
|
+
There is a possibility to add non reactive information in the global state:
|
|
305
|
+
|
|
306
|
+
```tsx
|
|
307
|
+
const useCount = createGlobalState(0, { metadata: { renders: 0 } });
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
How to use it?
|
|
529
311
|
|
|
530
|
-
|
|
312
|
+
```tsx
|
|
313
|
+
const [count, , metadata] = useCount();
|
|
531
314
|
|
|
532
|
-
|
|
533
|
-
extends GlobalStoreAbstract<State, Metadata, ActionsConfig>
|
|
315
|
+
metadata.renders += 1;
|
|
534
316
|
```
|
|
535
317
|
|
|
536
|
-
|
|
318
|
+
## π― Ready to Try It?
|
|
319
|
+
|
|
320
|
+
π¦ **NPM Package:** [react-hooks-global-states](https://www.npmjs.com/package/react-hooks-global-states)
|
|
321
|
+
|
|
322
|
+
π Simplify your **global state management** in React & React Native today! π
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-hooks-global-states",
|
|
3
|
-
"version": "7.0.
|
|
4
|
-
"description": "This is a package to easily handling global-state across your react-components
|
|
3
|
+
"version": "7.0.12",
|
|
4
|
+
"description": "This is a package to easily handling global-state across your react-components using hooks.",
|
|
5
5
|
"main": "./bundle.js",
|
|
6
6
|
"types": "./index.d.ts",
|
|
7
7
|
"sideEffects": false,
|
|
@@ -103,6 +103,7 @@
|
|
|
103
103
|
"url": "git+https://github.com/johnny-quesada-developer/react-hooks-global-states.git"
|
|
104
104
|
},
|
|
105
105
|
"keywords": [
|
|
106
|
+
"preact",
|
|
106
107
|
"react",
|
|
107
108
|
"redux",
|
|
108
109
|
"state",
|
|
@@ -111,8 +112,7 @@
|
|
|
111
112
|
"global-state",
|
|
112
113
|
"context",
|
|
113
114
|
"typescript",
|
|
114
|
-
"react-native"
|
|
115
|
-
"async-storage"
|
|
115
|
+
"react-native"
|
|
116
116
|
],
|
|
117
117
|
"author": "Johnny Quesada",
|
|
118
118
|
"license": "MIT",
|