react-global-state-hooks 1.0.14 → 1.0.16
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 +617 -59
- package/lib/GlobalStore.d.ts +43 -46
- package/lib/GlobalStore.d.ts.map +1 -1
- package/lib/GlobalStore.js +88 -152
- package/lib/GlobalStore.types.d.ts +25 -0
- package/lib/GlobalStore.types.d.ts.map +1 -0
- package/lib/{GlobalStoreTypes.js → GlobalStore.types.js} +0 -0
- package/package.json +2 -2
- package/README.advance.md +0 -123
- package/lib/GlobalStoreTypes.d.ts +0 -54
- package/lib/GlobalStoreTypes.d.ts.map +0 -1
package/README.md
CHANGED
|
@@ -1,86 +1,113 @@
|
|
|
1
1
|
# react-global-state-hooks
|
|
2
|
-
This is a package to easily handling global-state across your react-components **No-redux**, **No-context**
|
|
3
2
|
|
|
4
|
-
This
|
|
3
|
+
This is a package to easily handling global-state across your react components
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
This utility uses the **useState** hook within a subscription pattern and **HOFs** to create a more intuitive, atomic and easy way of sharing state between components
|
|
7
6
|
|
|
8
|
-
|
|
7
|
+
...
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
...
|
|
10
|
+
|
|
11
|
+
# Creating a global store
|
|
12
|
+
|
|
13
|
+
We are gonna create a global count example **useCountGlobal.ts**:
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { GlobalStore } from 'react-global-state-hooks';
|
|
13
17
|
|
|
14
18
|
// initialize your store with the default value of the same.
|
|
15
19
|
const countStore = new GlobalStore(0);
|
|
16
20
|
|
|
17
|
-
//
|
|
21
|
+
// get the hook
|
|
18
22
|
export const useCountGlobal = countStore.getHook();
|
|
19
23
|
|
|
24
|
+
// inside your component just call...
|
|
25
|
+
const [count, setCount] = useCountGlobal(); // no paremeters are needed since this is a global store
|
|
26
|
+
|
|
20
27
|
// That's it, that's a global store... Strongly typed, with a global-hook that we could reuse cross all our react-components.
|
|
28
|
+
|
|
29
|
+
// #### Optionally you are able to use a decoupled hook,
|
|
30
|
+
// #### This function is linked to the store hooks but is not a hook himself.
|
|
31
|
+
|
|
32
|
+
export const [getCount, sendCount] = countStore.getHookDecoupled();
|
|
33
|
+
|
|
34
|
+
// @example
|
|
35
|
+
console.log(getCount()); // 0;
|
|
36
|
+
|
|
37
|
+
// components subscribed to the global hook if there are so
|
|
38
|
+
sendCount(5);
|
|
39
|
+
|
|
40
|
+
console.log(getCount()); // 5;
|
|
21
41
|
```
|
|
22
42
|
|
|
23
|
-
|
|
24
|
-
|
|
43
|
+
...
|
|
44
|
+
|
|
45
|
+
...
|
|
46
|
+
|
|
47
|
+
# Implementing your global hook into your components
|
|
48
|
+
|
|
49
|
+
Let's say we have two components **MyFirstComponent**, **MySecondComponent**, in order to use our global hook they will look just like:
|
|
25
50
|
|
|
26
51
|
```JSX
|
|
27
|
-
import { useCountGlobal } from './
|
|
52
|
+
import { useCountGlobal } from './useCountGlobal'
|
|
28
53
|
|
|
29
54
|
const MyFirstComponent: React.FC = () => {
|
|
30
55
|
const [count, setter] = useCountGlobal();
|
|
31
|
-
const
|
|
56
|
+
const onClickAddOne = () => setter(count + 1);
|
|
32
57
|
|
|
33
|
-
return (<button
|
|
58
|
+
return (<button onClick={onClickAddOne}>{`count: ${count}`}</button>);
|
|
34
59
|
}
|
|
35
60
|
|
|
36
61
|
const MySecondComponent: React.FC = () => {
|
|
37
62
|
const [count, setter] = useCountGlobal();
|
|
38
|
-
const onClick = useCallback(() => setter(currentState => currentState + 1));
|
|
39
63
|
|
|
40
|
-
|
|
64
|
+
// it can also be use as a normal setter into a callback or other hooks
|
|
65
|
+
const onClickAddTwo = useCallback(() => setter(state => state + 2), [])
|
|
66
|
+
|
|
67
|
+
return (<button onClick={onClickAddOne}>{`count: ${count}`}</button>);
|
|
41
68
|
}
|
|
42
69
|
|
|
43
|
-
//
|
|
70
|
+
// It's so simple to share information between components
|
|
44
71
|
```
|
|
45
72
|
|
|
46
73
|
Note that the only difference between this and the default **useState** hook is that you are not adding the initial value, cause you already did that when you created the store:
|
|
47
74
|
|
|
48
|
-
```
|
|
75
|
+
```ts
|
|
49
76
|
const countStore = new GlobalStore(0);
|
|
50
77
|
```
|
|
51
78
|
|
|
52
|
-
|
|
79
|
+
...
|
|
53
80
|
|
|
54
|
-
|
|
81
|
+
...
|
|
55
82
|
|
|
56
|
-
|
|
57
|
-
// The FIRST parameter is the initial value of the state
|
|
58
|
-
// The Second parameter is an API to restrict access to the state, will talk about that later on [README]:./README.advance.md
|
|
59
|
-
// The Third parameter is the key that will be used on the local-storage
|
|
60
|
-
const countStore = new GlobalStore(0, null, 'GLOBAL_COUNT');
|
|
61
|
-
```
|
|
83
|
+
# Persisting state into localhost
|
|
62
84
|
|
|
63
|
-
|
|
85
|
+
if you want to persist the value of the store into the. **localstorage**, you only have to add the property **localStorageKey** into the configuration parameter.
|
|
64
86
|
|
|
65
|
-
```
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
const [count, setCount] = useCountGlobal();
|
|
69
|
-
const onClickAddOne = () => setCount(count + 1);
|
|
87
|
+
```ts
|
|
88
|
+
const countStore = new GlobalStore(0, {
|
|
89
|
+
localStorageKey: 'my_persisted_state',
|
|
70
90
|
|
|
71
|
-
|
|
72
|
-
|
|
91
|
+
// by default, the state is encrypted to base64, but you can disable it, or use a custom encryptor
|
|
92
|
+
encrypt: false,
|
|
93
|
+
|
|
94
|
+
// by default, the state is encrypted to base64, but you can disable it, or use a custom decrypt
|
|
95
|
+
decrypt: false,
|
|
96
|
+
});
|
|
73
97
|
```
|
|
74
98
|
|
|
75
|
-
|
|
99
|
+
...
|
|
76
100
|
|
|
77
|
-
|
|
101
|
+
...
|
|
78
102
|
|
|
79
|
-
|
|
103
|
+
# Decoupled hook
|
|
80
104
|
|
|
105
|
+
If you want to access the global state outside a component or outside a hook, or without subscribing the component to the state changes...
|
|
81
106
|
|
|
82
|
-
|
|
83
|
-
|
|
107
|
+
This is especially useful when you want to create components that have edition access to a certain store, but they actually don't need to be reactive to the state changes, like a search component that just need to get the current state every time that is going to search the data; but actually don't need to be subscribed to the changes over the collection he is going to be filtering.
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
import { GlobalStore } from 'react-global-state-hooks';
|
|
84
111
|
|
|
85
112
|
const countStore = new GlobalStore(0);
|
|
86
113
|
|
|
@@ -88,14 +115,15 @@ const countStore = new GlobalStore(0);
|
|
|
88
115
|
export const useCountGlobal = countStore.getHook();
|
|
89
116
|
|
|
90
117
|
// this functions are not hooks, and they can be used in whatever place into your code, ClassComponents, OtherHooks, Services etc.
|
|
91
|
-
export const [
|
|
92
|
-
|
|
118
|
+
export const [getCount, sendCount] = countStore.getHookDecoupled();
|
|
93
119
|
```
|
|
94
120
|
|
|
95
|
-
Let's see a trivial example:
|
|
121
|
+
Let's see a trivial example:
|
|
122
|
+
|
|
123
|
+
...
|
|
96
124
|
|
|
97
125
|
```JSX
|
|
98
|
-
import { useCountGlobal,
|
|
126
|
+
import { useCountGlobal, sendCount } from './useCountGlobal'
|
|
99
127
|
|
|
100
128
|
const CountDisplayerComponent: React.FC = () => {
|
|
101
129
|
const [count] = useCountGlobal();
|
|
@@ -103,32 +131,562 @@ const CountDisplayerComponent: React.FC = () => {
|
|
|
103
131
|
return (<label>{count}<label/>);
|
|
104
132
|
}
|
|
105
133
|
|
|
134
|
+
// here we have a separate component that is gonna handle the state of the previous component we created,
|
|
135
|
+
// this new component is not gonna be affected by the changes applied on <CountDisplayerComponent/>
|
|
106
136
|
// Stage2 does not need to be updated once the global count changes
|
|
107
137
|
const CountManagerComponent: React.FC = () => {
|
|
108
|
-
const increaseClick = () =>
|
|
109
|
-
const decreaseClick = () =>
|
|
138
|
+
const increaseClick = useCallback(() => sendCount(count => count + 1), []);
|
|
139
|
+
const decreaseClick = useCallback(() => sendCount(count => count - 1), []);
|
|
110
140
|
|
|
111
141
|
return (<>
|
|
112
|
-
<button
|
|
113
|
-
<button
|
|
142
|
+
<button onClick={increaseClick} >increase</button>
|
|
143
|
+
<button onClick={decreaseClick} >decrease</button>
|
|
114
144
|
</>);
|
|
115
145
|
}
|
|
116
146
|
```
|
|
117
147
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
148
|
+
...
|
|
149
|
+
|
|
150
|
+
...
|
|
151
|
+
|
|
152
|
+
# Restricting the manipulation of the global **state**
|
|
153
|
+
|
|
154
|
+
## Who hate reducers?
|
|
155
|
+
|
|
156
|
+
It's super common to have the wish or the necessity of restricting the manipulation of the **state** through a specific set of actions or manipulations...**Dispatches**? **Actions**? Let's make it simple BY adding a custom **API** to the configuration of our **GlobalStore**
|
|
157
|
+
...
|
|
158
|
+
|
|
159
|
+
```ts
|
|
160
|
+
const initialValue = 0;
|
|
161
|
+
|
|
162
|
+
const config = {
|
|
163
|
+
// this is not reactive information that you could also store in the async storage
|
|
164
|
+
// upating the metadata will not trigger the onStateChanged method or any update on the components
|
|
165
|
+
metadata: null,
|
|
166
|
+
|
|
167
|
+
// The lifecycle callbacks are: onInit, onStateChanged, onSubscribed and computePreventStateChange
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const countStore = new GlobalStore(
|
|
171
|
+
initialValue,
|
|
172
|
+
config,
|
|
173
|
+
{
|
|
174
|
+
log: (message: string) => (): void => {
|
|
175
|
+
console.log(message);
|
|
176
|
+
},
|
|
177
|
+
|
|
178
|
+
increase(message: string) {
|
|
179
|
+
return (storeTools: StoreTools<number>) => {
|
|
180
|
+
this.log(message);
|
|
181
|
+
|
|
182
|
+
return storeTools.getState();
|
|
183
|
+
};
|
|
184
|
+
},
|
|
185
|
+
|
|
186
|
+
decrease(message: string) {
|
|
187
|
+
return (storeTools: StoreTools<number>) => {
|
|
188
|
+
this.log(message);
|
|
189
|
+
|
|
190
|
+
return storeTools.getState();
|
|
191
|
+
};
|
|
192
|
+
},
|
|
193
|
+
} as const // the -as const- is necessary to avoid typescript errors
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
// the way to get the hook is the same as for simple setters
|
|
197
|
+
const useCountStore = countStore.getHook();
|
|
198
|
+
|
|
199
|
+
// now instead of a setState method, you'll get an actions object
|
|
200
|
+
// that contains all the actions that you defined in the setterConfig
|
|
201
|
+
const [count, countActions] = useCountStore();
|
|
202
|
+
|
|
203
|
+
// count is the current state - 0 (number)
|
|
204
|
+
// countActions is an object that contains all the actions that you defined in the setterConfig
|
|
205
|
+
// countActions.increase(); // this will increase the count by 1, returns the new count (number)
|
|
206
|
+
// countActions.decrease(); // this will decrease the count by 1, returns the new count (number)
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
...
|
|
210
|
+
|
|
211
|
+
# Configuration callbacks
|
|
212
|
+
|
|
213
|
+
## config.onInit
|
|
214
|
+
|
|
215
|
+
This method will be called once the store is created after the constructor,
|
|
216
|
+
|
|
217
|
+
@examples
|
|
218
|
+
|
|
219
|
+
```ts
|
|
220
|
+
import { GlobalStore } from 'react-global-state-hooks';
|
|
221
|
+
|
|
222
|
+
const initialValue = 0;
|
|
223
|
+
|
|
224
|
+
const store = new GlobalStore(0, {
|
|
225
|
+
onInit: async ({ setMetadata, setState }) => {
|
|
226
|
+
const data = await someApiCall();
|
|
227
|
+
|
|
228
|
+
setState(data);
|
|
229
|
+
setMetadata({ isDataUpdated: true });
|
|
230
|
+
},
|
|
231
|
+
});
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
...
|
|
235
|
+
|
|
236
|
+
## config.onStateChanged
|
|
237
|
+
|
|
238
|
+
This method will be called every time the state is changed
|
|
239
|
+
|
|
240
|
+
@examples
|
|
241
|
+
|
|
242
|
+
```ts
|
|
243
|
+
import { GlobalStore } from 'react-global-state-hooks';
|
|
244
|
+
|
|
245
|
+
const store = new GlobalStore(0, {
|
|
246
|
+
onStateChanged: ({ getState }) => {
|
|
247
|
+
const state = getState();
|
|
248
|
+
|
|
249
|
+
console.log(state);
|
|
250
|
+
},
|
|
251
|
+
});
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
...
|
|
255
|
+
|
|
256
|
+
## config.onSubscribed
|
|
257
|
+
|
|
258
|
+
This method will be called every time a component is subscribed to the store
|
|
259
|
+
|
|
260
|
+
@examples
|
|
261
|
+
|
|
262
|
+
```ts
|
|
263
|
+
import { GlobalStore } from 'react-global-state-hooks';
|
|
264
|
+
|
|
265
|
+
const store = new GlobalStore(0, {
|
|
266
|
+
onSubscribed: ({ getState }) => {
|
|
267
|
+
console.log('A component was subscribed to the store');
|
|
268
|
+
},
|
|
269
|
+
});
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
...
|
|
273
|
+
|
|
274
|
+
## config.computePreventStateChange
|
|
275
|
+
|
|
276
|
+
This method will be called every time the state is going to be changed, if it returns true the state won't be changed
|
|
277
|
+
|
|
278
|
+
@examples
|
|
279
|
+
|
|
280
|
+
```ts
|
|
281
|
+
import { GlobalStore } from 'react-global-state-hooks';
|
|
282
|
+
|
|
283
|
+
const store = new GlobalStore(0, {
|
|
284
|
+
computePreventStateChange: ({ getState }) => {
|
|
285
|
+
const state = getState();
|
|
286
|
+
const shouldPrevent = state < 0;
|
|
287
|
+
|
|
288
|
+
if (shouldPrevent) return true;
|
|
289
|
+
|
|
290
|
+
return false;
|
|
291
|
+
},
|
|
292
|
+
});
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
...
|
|
296
|
+
|
|
297
|
+
...
|
|
298
|
+
|
|
299
|
+
...
|
|
300
|
+
|
|
301
|
+
...
|
|
302
|
+
|
|
303
|
+
...
|
|
304
|
+
|
|
305
|
+
# Examples and Comparison:
|
|
306
|
+
|
|
307
|
+
## 1. Lets try to share some state between components
|
|
308
|
+
|
|
309
|
+
### **With the GlobalStore approach, it will look like this:**
|
|
310
|
+
|
|
311
|
+
```tsx
|
|
312
|
+
type TUser = {
|
|
313
|
+
name: string;
|
|
314
|
+
email: string;
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
const useUserStore = new GlobalStore<TUser>({
|
|
318
|
+
name: null,
|
|
319
|
+
email: null,
|
|
320
|
+
}).getHook();
|
|
321
|
+
|
|
322
|
+
const Component = () => {
|
|
323
|
+
const [currentUser] = useUserStore();
|
|
324
|
+
|
|
325
|
+
return <Text>{currentUser.name}</Text>;
|
|
326
|
+
};
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
## Simple, right?
|
|
330
|
+
|
|
331
|
+
### Let's now see how this same thing would look like by using context:
|
|
332
|
+
|
|
333
|
+
```tsx
|
|
334
|
+
type TUser = {
|
|
335
|
+
name: string;
|
|
336
|
+
email: string;
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
const UserContext = createContext<{
|
|
340
|
+
currentUser: TUser;
|
|
341
|
+
}>({
|
|
342
|
+
currentUser: null,
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
const UserProvider: React.FC<PropsWithChildren> = ({ children }) => {
|
|
346
|
+
const [currentUser, setCurrentUser] = useState<TUser>(null);
|
|
347
|
+
|
|
348
|
+
// ...get current user information
|
|
349
|
+
|
|
350
|
+
return (
|
|
351
|
+
<UserContext.Provider value={{ currentUser }}>
|
|
352
|
+
{children}
|
|
353
|
+
</UserContext.Provider>
|
|
354
|
+
);
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
const Component = () => {
|
|
358
|
+
const { currentUser } = useContext(UserContext);
|
|
359
|
+
|
|
360
|
+
return <Text>{currentUser.name}</Text>;
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
const App = () => {
|
|
364
|
+
return (
|
|
365
|
+
<UserProvider>
|
|
366
|
+
<Component />
|
|
367
|
+
</UserProvider>
|
|
368
|
+
);
|
|
369
|
+
};
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
### We already are able to notice a couple of extra lines right?
|
|
373
|
+
|
|
374
|
+
Let's now add another simple store to the equation
|
|
375
|
+
|
|
376
|
+
### **With the GlobalStore approach, it will look like this:**
|
|
377
|
+
|
|
378
|
+
```tsx
|
|
379
|
+
type TUser = {
|
|
380
|
+
name: string;
|
|
381
|
+
email: string;
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
const useUserStore = new GlobalStore<TUser>({
|
|
385
|
+
name: null,
|
|
386
|
+
email: null,
|
|
387
|
+
}).getHook();
|
|
388
|
+
|
|
389
|
+
// we create the store
|
|
390
|
+
const useCountStore = new GlobalStore(0).getHook();
|
|
391
|
+
|
|
392
|
+
const Component = () => {
|
|
393
|
+
const [currentUser] = useUserStore();
|
|
394
|
+
|
|
395
|
+
// from the component we consume the new store
|
|
396
|
+
const [count, setCount] = useCountStore();
|
|
397
|
+
|
|
398
|
+
return <label>{currentUser.name}</label>;
|
|
399
|
+
};
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
With context, we'll have again to create all the boilerplate, and wrap the component into the new provider...
|
|
403
|
+
|
|
404
|
+
### **Lets see that**
|
|
405
|
+
|
|
406
|
+
```tsx
|
|
407
|
+
type TUser = {
|
|
408
|
+
name: string;
|
|
409
|
+
email: string;
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
const UserContext = createContext<{
|
|
413
|
+
currentUser: TUser;
|
|
414
|
+
}>({
|
|
415
|
+
currentUser: null,
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
// let's create the context
|
|
419
|
+
const CountContext = createContext({
|
|
420
|
+
count: 0,
|
|
421
|
+
setCount: (() => {
|
|
422
|
+
throw new Error('not implemented');
|
|
423
|
+
}) as Dispatch<SetStateAction<number>>,
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
const UserProvider: React.FC<PropsWithChildren> = ({ children }) => {
|
|
427
|
+
const [currentUser, setCurrentUser] = useState<TUser>(null);
|
|
428
|
+
|
|
429
|
+
// ...
|
|
430
|
+
|
|
431
|
+
return (
|
|
432
|
+
<UserContext.Provider value={{ currentUser }}>
|
|
433
|
+
{children}
|
|
434
|
+
</UserContext.Provider>
|
|
435
|
+
);
|
|
436
|
+
};
|
|
437
|
+
|
|
438
|
+
// we also need another provider
|
|
439
|
+
const CountProvider: React.FC<PropsWithChildren> = ({ children }) => {
|
|
440
|
+
const [count, setCount] = useState(0);
|
|
441
|
+
|
|
442
|
+
return (
|
|
443
|
+
<CountContext.Provider value={{ count, setCount }}>
|
|
444
|
+
{children}
|
|
445
|
+
</CountContext.Provider>
|
|
446
|
+
);
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
// we need to wrap the component into the new provider (this is for each future context)
|
|
450
|
+
const App = () => {
|
|
451
|
+
return (
|
|
452
|
+
<UserProvider>
|
|
453
|
+
<CountProvider>
|
|
454
|
+
<Component />
|
|
455
|
+
</CountProvider>
|
|
456
|
+
</UserProvider>
|
|
457
|
+
);
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
const Component = () => {
|
|
461
|
+
const { currentUser } = useContext(UserContext);
|
|
462
|
+
|
|
463
|
+
// finally we are able to get access to the new context...
|
|
464
|
+
const { count, setCount } = useContext(CountContext);
|
|
465
|
+
|
|
466
|
+
return <label>{currentUser.name}</label>;
|
|
467
|
+
};
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
In this example, we are able to see how every time along with creating a good amount of repetitive code, we also have to wrap the necessary components into the Provider... Also, notice how every time we need to modify the **App** component, even when the App component is not gonna use the new state.
|
|
471
|
+
|
|
472
|
+
### Let's make this a little more complex, now I want to implement custom methods for manipulating the count state, I also want to have the ability to modify the count state **without** having to be subscribed to the changes of the state... have you ever done that?
|
|
473
|
+
|
|
474
|
+
This is a common scenery, and guess what? in the **context** examples, we'll have to create another context, another provider, wrap and everything again...
|
|
475
|
+
|
|
476
|
+
## Let's see this time first the **context** approach
|
|
477
|
+
|
|
478
|
+
```tsx
|
|
479
|
+
type TUser = {
|
|
480
|
+
name: string;
|
|
481
|
+
email: string;
|
|
482
|
+
};
|
|
483
|
+
|
|
484
|
+
const UserContext = createContext<{
|
|
485
|
+
currentUser: TUser;
|
|
486
|
+
}>({
|
|
487
|
+
currentUser: null,
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
// let's remove the setter from this context
|
|
491
|
+
const CountContext = createContext({
|
|
492
|
+
count: 0,
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
// lets create another context to share the actions
|
|
496
|
+
const CountContextSetter = createContext({
|
|
497
|
+
increase: (): void => {
|
|
498
|
+
throw new Error('increase is not implemented');
|
|
499
|
+
},
|
|
500
|
+
decrease: (): void => {
|
|
501
|
+
throw new Error('decrease is not implemented');
|
|
502
|
+
},
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
const UserProvider: React.FC<PropsWithChildren> = ({ children }) => {
|
|
506
|
+
const [currentUser, setCurrentUser] = useState<TUser>(null);
|
|
507
|
+
|
|
508
|
+
// ...
|
|
509
|
+
|
|
510
|
+
return (
|
|
511
|
+
<UserContext.Provider value={{ currentUser }}>
|
|
512
|
+
{children}
|
|
513
|
+
</UserContext.Provider>
|
|
514
|
+
);
|
|
515
|
+
};
|
|
516
|
+
|
|
517
|
+
// To don't overcomplicate the example let's just add but providers into this component, that will be enough
|
|
518
|
+
const CountProvider: React.FC<PropsWithChildren> = ({ children }) => {
|
|
519
|
+
const [count, setCount] = useState(0);
|
|
520
|
+
|
|
521
|
+
const increase = () => setCount(count + 1);
|
|
522
|
+
const decrease = () => setCount(count - 1);
|
|
523
|
+
|
|
524
|
+
return (
|
|
525
|
+
//one context is gonna share the edition of the state
|
|
526
|
+
<CountContext.Provider value={{ count }}>
|
|
527
|
+
{/* this second component will share the mutations of the state */}
|
|
528
|
+
<CountContextSetter.Provider value={{ increase, decrease }}>
|
|
529
|
+
{children}
|
|
530
|
+
</CountContextSetter.Provider>
|
|
531
|
+
</CountContext.Provider>
|
|
532
|
+
);
|
|
533
|
+
};
|
|
534
|
+
|
|
535
|
+
// Since we used the same provider we don't need to modify the **App** component, but we do are **Wrapping** everything into one more **Provider**
|
|
536
|
+
const App = () => {
|
|
537
|
+
return (
|
|
538
|
+
<UserProvider>
|
|
539
|
+
<CountProvider>
|
|
540
|
+
{/* lets create two componets instead of one */}
|
|
541
|
+
<ComponentSetter />
|
|
542
|
+
<Component />
|
|
543
|
+
</CountProvider>
|
|
544
|
+
</UserProvider>
|
|
545
|
+
);
|
|
546
|
+
};
|
|
547
|
+
|
|
548
|
+
const ComponentSetter = () => {
|
|
549
|
+
const { increase, decrease } = useContext(CountContextSetter);
|
|
550
|
+
|
|
551
|
+
return (
|
|
552
|
+
<>
|
|
553
|
+
<button onPress={increase}>Increase</button>
|
|
554
|
+
<button onPress={decrease}>Decrease</button>
|
|
555
|
+
</>
|
|
556
|
+
);
|
|
557
|
+
};
|
|
558
|
+
|
|
559
|
+
const Component = () => {
|
|
560
|
+
const { currentUser } = useContext(UserContext);
|
|
561
|
+
|
|
562
|
+
// finally we are able to get access to the new context...
|
|
563
|
+
const { count } = useContext(CountContext);
|
|
564
|
+
|
|
565
|
+
return (
|
|
566
|
+
<>
|
|
567
|
+
<label>{currentUser.name}</label>
|
|
568
|
+
<label>{count}</label>
|
|
569
|
+
</>
|
|
570
|
+
);
|
|
571
|
+
};
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
Wow, a lot!!! just to be able to separate the mutations... and have mutations!!
|
|
575
|
+
|
|
576
|
+
### it would be easier with the GlobalStore? Let's see.
|
|
577
|
+
|
|
578
|
+
```tsx
|
|
579
|
+
type TUser = {
|
|
580
|
+
name: string;
|
|
581
|
+
email: string;
|
|
582
|
+
};
|
|
583
|
+
|
|
584
|
+
const useUser = new GlobalStore<TUser>({
|
|
585
|
+
name: null,
|
|
586
|
+
email: null,
|
|
587
|
+
}).getHook();
|
|
588
|
+
|
|
589
|
+
// let's modify the store to add custom actions, the second parameter is configuration let's just pass null for now
|
|
590
|
+
const countStore = new GlobalStore(0, null, {
|
|
591
|
+
increase() {
|
|
592
|
+
return ({ setState }: StoreTools<number>) => {
|
|
593
|
+
setState((state) => state + 1);
|
|
594
|
+
};
|
|
595
|
+
},
|
|
596
|
+
|
|
597
|
+
decrease() {
|
|
598
|
+
return ({ setState }: StoreTools<number>) => {
|
|
599
|
+
setState((state) => state - 1);
|
|
600
|
+
};
|
|
601
|
+
},
|
|
602
|
+
} as const);
|
|
603
|
+
|
|
604
|
+
const useCount = countStore.getHook();
|
|
605
|
+
|
|
606
|
+
// this actions don't use hooks, but are connected to the store and all the subscribers will be notified
|
|
607
|
+
const [, countActions] = countStore.getHookDecoupled();
|
|
608
|
+
|
|
609
|
+
// this component is not subscribed to the store, so it will not be notified when the state changes
|
|
610
|
+
const ComponentSetter = () => {
|
|
611
|
+
return (
|
|
612
|
+
<>
|
|
613
|
+
<button onPress={countActions.increase}>Increase</button>
|
|
614
|
+
<button onPress={countActions.decrease}>Decrease</button>
|
|
615
|
+
</>
|
|
616
|
+
);
|
|
617
|
+
};
|
|
618
|
+
|
|
619
|
+
// this component is subscribed to the store, so it will be notified when the state changes
|
|
620
|
+
const Component = () => {
|
|
621
|
+
const [user] = useUser();
|
|
622
|
+
const [count, actions] = useCount();
|
|
623
|
+
|
|
624
|
+
return (
|
|
625
|
+
<>
|
|
626
|
+
<label>{count}</label>
|
|
627
|
+
</>
|
|
628
|
+
);
|
|
629
|
+
};
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
### So let's analyze what happened
|
|
633
|
+
|
|
634
|
+
To restrict the state manipulations with the custom actions, we just need to add a third parameter to the store.
|
|
635
|
+
|
|
636
|
+
```ts
|
|
637
|
+
const countStore = new GlobalStore(0, null, {
|
|
638
|
+
log: (action: string) => () => console.log(action),
|
|
639
|
+
|
|
640
|
+
// every action is a function that returns a function that receives the store tools
|
|
641
|
+
increase() {
|
|
642
|
+
return ({ setState, getState }: StoreTools<number>): number => {
|
|
643
|
+
setState((state) => state + 1);
|
|
644
|
+
|
|
645
|
+
// actions are able to communicate between them
|
|
646
|
+
this.log('increase');
|
|
647
|
+
|
|
648
|
+
return getState();
|
|
649
|
+
};
|
|
650
|
+
},
|
|
651
|
+
} as const);
|
|
652
|
+
|
|
653
|
+
// the const is necessary to avoid typescript errors
|
|
654
|
+
```
|
|
655
|
+
|
|
656
|
+
All the library is strongly typed, we use generics to return the correct data type in each action.
|
|
657
|
+
|
|
658
|
+
```ts
|
|
659
|
+
const [, actions] = countStore.getHookDecoupled();
|
|
660
|
+
|
|
661
|
+
// for example the type of actions.increase will be: () => number
|
|
662
|
+
// just in case, even the parameters of the actions are gonna be exposed through TS
|
|
663
|
+
```
|
|
664
|
+
|
|
665
|
+
## getHookDecoupled
|
|
666
|
+
|
|
667
|
+
### **getHookDecoupled** returns a tuple with the state and the actions,
|
|
668
|
+
|
|
669
|
+
This is so useful when you want to use the actions without having to be subscribed to changes of the state.
|
|
670
|
+
There is also a third element in the tuple which is a function for getting the metadata of the store
|
|
671
|
+
|
|
672
|
+
### the metadata of the store is not reactive information which could be shared through the store
|
|
673
|
+
|
|
674
|
+
## Adding metadata to the store
|
|
675
|
+
|
|
676
|
+
```tsx
|
|
677
|
+
const [, , getMetadata] = new GlobalStore(0, {
|
|
678
|
+
metadata: {
|
|
679
|
+
isStoredSyncronized: false,
|
|
680
|
+
},
|
|
681
|
+
}).getHookDecoupled();
|
|
682
|
+
|
|
683
|
+
console.log(getMetadata().isStoredSyncronized); // false
|
|
684
|
+
```
|
|
121
685
|
|
|
122
|
-
|
|
123
|
-
1. Using REACT's simplest and default way to deal with the state.
|
|
124
|
-
2. Adding partial state designations (This is not on useState default functionality)
|
|
125
|
-
3. Added availability to create actions and decoupled access to the states, no more connects, and dispatches, just call your actions as a normal service of whatever other libraries.
|
|
126
|
-
4. This library is already taking care of avoiding re-renders if the new state does not have changes
|
|
127
|
-
5. This tool also take care for you to avoid **localStorage** data to lose the data types that you stored. For example when you are using datetimes
|
|
128
|
-
6. This library also is taking care of batching multiple stores updates by using **React unstable_batchedUpdates**; this is a problem that the **useState** have when you call multiple **setStates** into async flows as setTimeout
|
|
686
|
+
The setMetadata is part of the store tools, so it can be used in the actions, but againg the metadata is not reactive!! so it will not trigger a re-render on the subscribers
|
|
129
687
|
|
|
130
|
-
|
|
131
|
-
Are concern about performance? this library is for you, instead of handling huge complex stores with options like redux, or by passing the setState to a context Provider (because of the limitations that the context has)... You should just use this library, we are using the more atomic and 'native' way that REACT gives to handle the state, and that is the hook **useState**...
|
|
688
|
+
...
|
|
132
689
|
|
|
133
|
-
|
|
690
|
+
...
|
|
134
691
|
|
|
692
|
+
# That's it for now!! hope you enjoy coding!!
|