react-native-global-state-hooks 2.1.8 → 2.1.10
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 +498 -73
- package/lib/GlobalStore.d.ts.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -138,83 +138,72 @@ Here is an example of how you could create your custom store that for example st
|
|
|
138
138
|
You could just use this code right as it is by just adding also into your project **@react-native-async-storage** or whatever another async storage library.
|
|
139
139
|
|
|
140
140
|
```ts
|
|
141
|
-
|
|
141
|
+
/**
|
|
142
|
+
* GlobalStore is an store that could also persist the state in the async storage
|
|
143
|
+
* @template {TState} TState - The state of the store
|
|
144
|
+
* @template {TMetadata} TMetadata - The metadata of the store, it must contain a readonly property called isAsyncStorageReady which cannot be set from outside the store
|
|
145
|
+
* @template {TStateSetter} TStateSetter - The storeActionsConfig of the store
|
|
146
|
+
*/
|
|
147
|
+
export class GlobalStore<
|
|
142
148
|
TState,
|
|
143
|
-
TMetadata extends { readonly isAsyncStorageReady: never },
|
|
144
149
|
// this restriction is needed to avoid the consumers to set the isAsyncStorageReady property from outside the store,
|
|
145
|
-
// even when the value will be ignored is better to avoid it to avoid confusion
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
TState,
|
|
157
|
-
Omit<TMetadata, 'isAsyncStorageReady'> & {
|
|
158
|
-
readonly isAsyncStorageReady: boolean;
|
|
159
|
-
},
|
|
160
|
-
TStateSetter
|
|
161
|
-
> {
|
|
162
|
-
protected config: NonNullable<
|
|
163
|
-
GlobalStoreConfig<
|
|
164
|
-
TState,
|
|
165
|
-
Omit<TMetadata, 'isAsyncStorageReady'> & {
|
|
166
|
-
readonly isAsyncStorageReady: boolean;
|
|
167
|
-
},
|
|
168
|
-
NonNullable<TStateSetter>
|
|
169
|
-
>
|
|
170
|
-
> & {
|
|
171
|
-
asyncStorageKey: string;
|
|
172
|
-
};
|
|
150
|
+
// ... even when the value will be ignored is better to avoid it to avoid confusion
|
|
151
|
+
TMetadata extends { readonly isAsyncStorageReady: never },
|
|
152
|
+
TStateSetter extends StorageSetter<TState, TMetadata> = StateSetter<TState>
|
|
153
|
+
> extends GlobalStoreBase<TState, StorageMetadata<TMetadata>, TStateSetter> {
|
|
154
|
+
/**
|
|
155
|
+
* Config for the async storage
|
|
156
|
+
* includes the asyncStorageKey and the metadata which will be used to determine if the async storage is ready or not
|
|
157
|
+
* @template {TState} TState - The state of the store
|
|
158
|
+
* @template {TMetadata} TMetadata - The metadata of the store
|
|
159
|
+
* @template {TStateSetter} TStateSetter - The storeActionsConfig of the store
|
|
160
|
+
**/
|
|
161
|
+
protected config: StorageConfig<TState, TMetadata, TStateSetter>;
|
|
173
162
|
|
|
163
|
+
/**
|
|
164
|
+
* Creates a new instance of the GlobalStore
|
|
165
|
+
* @param {TState} state - The initial state of the store
|
|
166
|
+
* @param {GlobalStoreConfig<TState, TMetadata, ActionCollectionConfig<TState, TMetadata> | StateSetter<TState>> & { asyncStorageKey: string; }} config - The config of the store
|
|
167
|
+
* @param {GlobalStoreConfig<TState, TMetadata, ActionCollectionConfig<TState, TMetadata> | StateSetter<TState>> & { asyncStorageKey: string; }} config.metadata - The metadata of the store which will be used to determine if the async storage is ready or not, also it could store no reactive data
|
|
168
|
+
* @param {GlobalStoreConfig<TState, TMetadata, ActionCollectionConfig<TState, TMetadata> | StateSetter<TState>> & { asyncStorageKey: string; }} config.asyncStorageKey - The key of the async storage
|
|
169
|
+
* @param {GlobalStoreConfig<TState, TMetadata, ActionCollectionConfig<TState, TMetadata> | StateSetter<TState>> & { asyncStorageKey: string; }} config.onInit - The callback that will be called once the store is created
|
|
170
|
+
* @param {GlobalStoreConfig<TState, TMetadata, ActionCollectionConfig<TState, TMetadata> | StateSetter<TState>> & { asyncStorageKey: string; }} config.onStateChange - The callback that will be called once the state is changed
|
|
171
|
+
* @param {GlobalStoreConfig<TState, TMetadata, ActionCollectionConfig<TState, TMetadata> | StateSetter<TState>> & { asyncStorageKey: string; }} config.onSubscribed - The callback that will be called every time a new component is subscribed to the store
|
|
172
|
+
* @param {GlobalStoreConfig<TState, TMetadata, ActionCollectionConfig<TState, TMetadata> | StateSetter<TState>> & { asyncStorageKey: string; }} config.computePreventStateChange - The callback that will be called before the state is changed, if it returns true the state will not be changed
|
|
173
|
+
* @param {TStateSetter} setterConfig - The actions configuration object (optional) (default: null) if not null the store manipulation will be done through the actions
|
|
174
|
+
*/
|
|
174
175
|
constructor(
|
|
175
176
|
state: TState,
|
|
176
|
-
config:
|
|
177
|
-
TState,
|
|
178
|
-
TMetadata,
|
|
179
|
-
ActionCollectionConfig<TState, TMetadata> | StateSetter<TState>
|
|
180
|
-
> & {
|
|
181
|
-
asyncStorageKey: string;
|
|
182
|
-
},
|
|
177
|
+
config: StorageConfig<TState, TMetadata, TStateSetter> | null = null,
|
|
183
178
|
setterConfig: TStateSetter | null = null
|
|
184
179
|
) {
|
|
185
|
-
|
|
186
|
-
GlobalStoreConfig<
|
|
187
|
-
TState,
|
|
188
|
-
Omit<TMetadata, 'isAsyncStorageReady'> & {
|
|
189
|
-
readonly isAsyncStorageReady: boolean;
|
|
190
|
-
},
|
|
191
|
-
NonNullable<TStateSetter>
|
|
192
|
-
>
|
|
193
|
-
> & {
|
|
194
|
-
asyncStorageKey: string;
|
|
195
|
-
};
|
|
196
|
-
|
|
197
|
-
const { onInit: onInitConfig, ...configParameters } = config as TConfig;
|
|
180
|
+
const { onInit, asyncStorageKey, ...configParameters } = config ?? {};
|
|
198
181
|
|
|
199
182
|
super(state, configParameters, setterConfig as TStateSetter);
|
|
200
183
|
|
|
184
|
+
// if there is not async storage key this is not a persistent store
|
|
185
|
+
const isAsyncStorageReady = asyncStorageKey ? false : null;
|
|
186
|
+
|
|
201
187
|
this.config = {
|
|
202
188
|
...config,
|
|
203
189
|
metadata: {
|
|
204
190
|
...configParameters.metadata,
|
|
205
|
-
isAsyncStorageReady
|
|
191
|
+
isAsyncStorageReady,
|
|
206
192
|
},
|
|
207
|
-
} as
|
|
193
|
+
} as StorageConfig<TState, TMetadata, TStateSetter>;
|
|
194
|
+
|
|
195
|
+
const hasInitCallbacks = !!(asyncStorageKey || onInit);
|
|
196
|
+
if (!hasInitCallbacks) return;
|
|
208
197
|
|
|
209
198
|
const parameters = this.getConfigCallbackParam({});
|
|
210
199
|
|
|
211
200
|
this.onInit(parameters);
|
|
212
|
-
|
|
201
|
+
onInit?.(parameters);
|
|
213
202
|
}
|
|
214
203
|
|
|
215
204
|
/**
|
|
216
205
|
* This method will be called once the store is created after the constructor,
|
|
217
|
-
* this method is different from the onInit of the
|
|
206
|
+
* this method is different from the onInit of the confg property and it won't be overriden
|
|
218
207
|
*/
|
|
219
208
|
protected onInit = async ({
|
|
220
209
|
setState,
|
|
@@ -222,12 +211,12 @@ export class GlobalStoreAsync<
|
|
|
222
211
|
getMetadata,
|
|
223
212
|
}: StateConfigCallbackParam<
|
|
224
213
|
TState,
|
|
225
|
-
|
|
226
|
-
readonly isAsyncStorageReady: boolean;
|
|
227
|
-
},
|
|
214
|
+
StorageMetadata<TMetadata>,
|
|
228
215
|
NonNullable<TStateSetter>
|
|
229
216
|
>) => {
|
|
230
217
|
const { asyncStorageKey } = this.config;
|
|
218
|
+
if (!asyncStorageKey) return;
|
|
219
|
+
|
|
231
220
|
const storedItem: string = await asyncStorage.getItem(asyncStorageKey);
|
|
232
221
|
|
|
233
222
|
setMetadata({
|
|
@@ -247,12 +236,11 @@ export class GlobalStoreAsync<
|
|
|
247
236
|
getState,
|
|
248
237
|
}: StateChangesParam<
|
|
249
238
|
TState,
|
|
250
|
-
|
|
251
|
-
readonly isAsyncStorageReady: boolean;
|
|
252
|
-
},
|
|
239
|
+
StorageMetadata<TMetadata>,
|
|
253
240
|
NonNullable<TStateSetter>
|
|
254
241
|
>) => {
|
|
255
242
|
const { asyncStorageKey } = this.config;
|
|
243
|
+
if (!asyncStorageKey) return;
|
|
256
244
|
|
|
257
245
|
const state = getState();
|
|
258
246
|
const formattedObject = formatToStore(state, {
|
|
@@ -262,6 +250,76 @@ export class GlobalStoreAsync<
|
|
|
262
250
|
asyncStorage.setItem(asyncStorageKey, formattedObject);
|
|
263
251
|
};
|
|
264
252
|
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Metadata of the store
|
|
256
|
+
* @template {TMetadata} TMetadata - The metadata type which also contains the isAsyncStorageReady property
|
|
257
|
+
*/
|
|
258
|
+
type StorageMetadata<TMetadata> = Omit<TMetadata, 'isAsyncStorageReady'> & {
|
|
259
|
+
readonly isAsyncStorageReady: boolean | null;
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* The setter of the store
|
|
264
|
+
* @template {TState} TState - The state of the store
|
|
265
|
+
* @template {TMetadata} TMetadata - The metadata of the store, it must contain a readonly property called isAsyncStorageReady which cannot be set from outside the store
|
|
266
|
+
* */
|
|
267
|
+
type StorageSetter<TState, TMetadata> =
|
|
268
|
+
| ActionCollectionConfig<TState, StorageMetadata<TMetadata>>
|
|
269
|
+
| StateSetter<TState>
|
|
270
|
+
| null;
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Config for the async storage
|
|
274
|
+
* includes the asyncStorageKey
|
|
275
|
+
* @template {TState} TState - The state of the store
|
|
276
|
+
* @template {TMetadata} TMetadata - The metadata of the store, it must contain a readonly property called isAsyncStorageReady which cannot be set from outside the store
|
|
277
|
+
* @template {TStateSetter} TStateSetter - The storeActionsConfig of the store
|
|
278
|
+
*/
|
|
279
|
+
type StorageConfig<
|
|
280
|
+
TState,
|
|
281
|
+
TMetadata extends { readonly isAsyncStorageReady: never },
|
|
282
|
+
TStateSetter extends
|
|
283
|
+
| ActionCollectionConfig<TState, StorageMetadata<TMetadata>>
|
|
284
|
+
| StateSetter<TState>
|
|
285
|
+
| null = StateSetter<TState>
|
|
286
|
+
> = {
|
|
287
|
+
asyncStorageKey?: string;
|
|
288
|
+
|
|
289
|
+
metadata?: TMetadata;
|
|
290
|
+
|
|
291
|
+
onInit?: (
|
|
292
|
+
parameters: StateConfigCallbackParam<
|
|
293
|
+
TState,
|
|
294
|
+
StorageMetadata<TMetadata>,
|
|
295
|
+
NonNullable<TStateSetter>
|
|
296
|
+
>
|
|
297
|
+
) => void;
|
|
298
|
+
|
|
299
|
+
onStateChanged?: (
|
|
300
|
+
parameters: StateChangesParam<
|
|
301
|
+
TState,
|
|
302
|
+
StorageMetadata<TMetadata>,
|
|
303
|
+
NonNullable<TStateSetter>
|
|
304
|
+
>
|
|
305
|
+
) => void;
|
|
306
|
+
|
|
307
|
+
onSubscribed?: (
|
|
308
|
+
parameters: StateConfigCallbackParam<
|
|
309
|
+
TState,
|
|
310
|
+
StorageMetadata<TMetadata>,
|
|
311
|
+
NonNullable<TStateSetter>
|
|
312
|
+
>
|
|
313
|
+
) => void;
|
|
314
|
+
|
|
315
|
+
computePreventStateChange?: (
|
|
316
|
+
parameters: StateChangesParam<
|
|
317
|
+
TState,
|
|
318
|
+
StorageMetadata<TMetadata>,
|
|
319
|
+
NonNullable<TStateSetter>
|
|
320
|
+
>
|
|
321
|
+
) => boolean;
|
|
322
|
+
};
|
|
265
323
|
```
|
|
266
324
|
|
|
267
325
|
The methods **formatToStore** and **formatFromStore** are part of another library of my [json-storage-formatter](https://www.npmjs.com/package/json-storage-formatter)...
|
|
@@ -439,24 +497,391 @@ const store = new GlobalStore(0, {
|
|
|
439
497
|
|
|
440
498
|
...
|
|
441
499
|
|
|
442
|
-
|
|
500
|
+
# Examples and Comparison:
|
|
443
501
|
|
|
444
|
-
1.
|
|
445
|
-
2. Adding partial state designations (This is not on useState default functionality)
|
|
446
|
-
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.
|
|
447
|
-
4. This library is already taking care of avoiding re-renders if the new state does not have changes
|
|
448
|
-
5. This tool also take care for you to avoid **async-storage\*** data to lose the data types that you stored. For example when you are using datetimes
|
|
502
|
+
## 1. Lets try to share some state between components
|
|
449
503
|
|
|
450
|
-
|
|
504
|
+
### **With the GlobalStore approach, it will look like this:**
|
|
451
505
|
|
|
452
|
-
|
|
506
|
+
```tsx
|
|
507
|
+
type TUser = {
|
|
508
|
+
name: string;
|
|
509
|
+
email: string;
|
|
510
|
+
};
|
|
453
511
|
|
|
454
|
-
|
|
512
|
+
const useUserStore = new GlobalStore<TUser>({
|
|
513
|
+
name: null,
|
|
514
|
+
email: null,
|
|
515
|
+
}).getHook();
|
|
455
516
|
|
|
456
|
-
|
|
517
|
+
const Component = () => {
|
|
518
|
+
const [currentUser] = useUserStore();
|
|
519
|
+
|
|
520
|
+
return <Text>{currentUser.name}</Text>;
|
|
521
|
+
};
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
## Simple, right?
|
|
525
|
+
|
|
526
|
+
### Let's now see how this same thing would look like by using context:
|
|
527
|
+
|
|
528
|
+
```tsx
|
|
529
|
+
type TUser = {
|
|
530
|
+
name: string;
|
|
531
|
+
email: string;
|
|
532
|
+
};
|
|
533
|
+
|
|
534
|
+
const UserContext = createContext<{
|
|
535
|
+
currentUser: TUser;
|
|
536
|
+
}>({
|
|
537
|
+
currentUser: null,
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
const UserProvider: React.FC<PropsWithChildren> = ({ children }) => {
|
|
541
|
+
const [currentUser, setCurrentUser] = useState<TUser>(null);
|
|
542
|
+
|
|
543
|
+
// ...get current user information
|
|
544
|
+
|
|
545
|
+
return (
|
|
546
|
+
<UserContext.Provider value={{ currentUser }}>
|
|
547
|
+
{children}
|
|
548
|
+
</UserContext.Provider>
|
|
549
|
+
);
|
|
550
|
+
};
|
|
551
|
+
|
|
552
|
+
const Component = () => {
|
|
553
|
+
const { currentUser } = useContext(UserContext);
|
|
554
|
+
|
|
555
|
+
return <Text>{currentUser.name}</Text>;
|
|
556
|
+
};
|
|
557
|
+
|
|
558
|
+
const App = () => {
|
|
559
|
+
return (
|
|
560
|
+
<UserProvider>
|
|
561
|
+
<Component />
|
|
562
|
+
</UserProvider>
|
|
563
|
+
);
|
|
564
|
+
};
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
### We already are able to notice a couple of extra lines right?
|
|
568
|
+
|
|
569
|
+
Let's now add another simple store to the equation
|
|
570
|
+
|
|
571
|
+
### **With the GlobalStore approach, it will look like this:**
|
|
572
|
+
|
|
573
|
+
```tsx
|
|
574
|
+
type TUser = {
|
|
575
|
+
name: string;
|
|
576
|
+
email: string;
|
|
577
|
+
};
|
|
578
|
+
|
|
579
|
+
const useUserStore = new GlobalStore<TUser>({
|
|
580
|
+
name: null,
|
|
581
|
+
email: null,
|
|
582
|
+
}).getHook();
|
|
583
|
+
|
|
584
|
+
// we create the store
|
|
585
|
+
const useCountStore = new GlobalStore(0).getHook();
|
|
586
|
+
|
|
587
|
+
const Component = () => {
|
|
588
|
+
const [currentUser] = useUserStore();
|
|
457
589
|
|
|
458
|
-
|
|
590
|
+
// from the component we consume the new store
|
|
591
|
+
const [count, setCount] = useCountStore();
|
|
592
|
+
|
|
593
|
+
return <Text>{currentUser.name}</Text>;
|
|
594
|
+
};
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
With context, we'll have again to create all the boilerplate, and wrap the component into the new provider...
|
|
598
|
+
|
|
599
|
+
### **Lets see that**
|
|
600
|
+
|
|
601
|
+
```tsx
|
|
602
|
+
type TUser = {
|
|
603
|
+
name: string;
|
|
604
|
+
email: string;
|
|
605
|
+
};
|
|
606
|
+
|
|
607
|
+
const UserContext = createContext<{
|
|
608
|
+
currentUser: TUser;
|
|
609
|
+
}>({
|
|
610
|
+
currentUser: null,
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
// let's create the context
|
|
614
|
+
const CountContext = createContext({
|
|
615
|
+
count: 0,
|
|
616
|
+
setCount: (() => {
|
|
617
|
+
throw new Error('not implemented');
|
|
618
|
+
}) as Dispatch<SetStateAction<number>>,
|
|
619
|
+
});
|
|
459
620
|
|
|
460
|
-
|
|
621
|
+
const UserProvider: React.FC<PropsWithChildren> = ({ children }) => {
|
|
622
|
+
const [currentUser, setCurrentUser] = useState<TUser>(null);
|
|
623
|
+
|
|
624
|
+
// ...
|
|
625
|
+
|
|
626
|
+
return (
|
|
627
|
+
<UserContext.Provider value={{ currentUser }}>
|
|
628
|
+
{children}
|
|
629
|
+
</UserContext.Provider>
|
|
630
|
+
);
|
|
631
|
+
};
|
|
632
|
+
|
|
633
|
+
// we also need another provider
|
|
634
|
+
const CountProvider: React.FC<PropsWithChildren> = ({ children }) => {
|
|
635
|
+
const [count, setCount] = useState(0);
|
|
636
|
+
|
|
637
|
+
return (
|
|
638
|
+
<CountContext.Provider value={{ count, setCount }}>
|
|
639
|
+
{children}
|
|
640
|
+
</CountContext.Provider>
|
|
641
|
+
);
|
|
642
|
+
};
|
|
643
|
+
|
|
644
|
+
// we need to wrap the component into the new provider (this is for each future context)
|
|
645
|
+
const App = () => {
|
|
646
|
+
return (
|
|
647
|
+
<UserProvider>
|
|
648
|
+
<CountProvider>
|
|
649
|
+
<Component />
|
|
650
|
+
</CountProvider>
|
|
651
|
+
</UserProvider>
|
|
652
|
+
);
|
|
653
|
+
};
|
|
654
|
+
|
|
655
|
+
const Component = () => {
|
|
656
|
+
const { currentUser } = useContext(UserContext);
|
|
657
|
+
|
|
658
|
+
// finally we are able to get access to the new context...
|
|
659
|
+
const { count, setCount } = useContext(CountContext);
|
|
660
|
+
|
|
661
|
+
return <Text>{currentUser.name}</Text>;
|
|
662
|
+
};
|
|
663
|
+
```
|
|
664
|
+
|
|
665
|
+
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.
|
|
666
|
+
|
|
667
|
+
### 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?
|
|
668
|
+
|
|
669
|
+
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...
|
|
670
|
+
|
|
671
|
+
## Let's see this time first the **context** approach
|
|
672
|
+
|
|
673
|
+
```tsx
|
|
674
|
+
type TUser = {
|
|
675
|
+
name: string;
|
|
676
|
+
email: string;
|
|
677
|
+
};
|
|
678
|
+
|
|
679
|
+
const UserContext = createContext<{
|
|
680
|
+
currentUser: TUser;
|
|
681
|
+
}>({
|
|
682
|
+
currentUser: null,
|
|
683
|
+
});
|
|
684
|
+
|
|
685
|
+
// let's remove the setter from this context
|
|
686
|
+
const CountContext = createContext({
|
|
687
|
+
count: 0,
|
|
688
|
+
});
|
|
689
|
+
|
|
690
|
+
// lets create another context to share the actions
|
|
691
|
+
const CountContextSetter = createContext({
|
|
692
|
+
increase: (): void => {
|
|
693
|
+
throw new Error('increase is not implemented');
|
|
694
|
+
},
|
|
695
|
+
decrease: (): void => {
|
|
696
|
+
throw new Error('decrease is not implemented');
|
|
697
|
+
},
|
|
698
|
+
});
|
|
699
|
+
|
|
700
|
+
const UserProvider: React.FC<PropsWithChildren> = ({ children }) => {
|
|
701
|
+
const [currentUser, setCurrentUser] = useState<TUser>(null);
|
|
702
|
+
|
|
703
|
+
// ...
|
|
704
|
+
|
|
705
|
+
return (
|
|
706
|
+
<UserContext.Provider value={{ currentUser }}>
|
|
707
|
+
{children}
|
|
708
|
+
</UserContext.Provider>
|
|
709
|
+
);
|
|
710
|
+
};
|
|
711
|
+
|
|
712
|
+
// To don't overcomplicate the example let's just add but providers into this component, that will be enough
|
|
713
|
+
const CountProvider: React.FC<PropsWithChildren> = ({ children }) => {
|
|
714
|
+
const [count, setCount] = useState(0);
|
|
715
|
+
|
|
716
|
+
const increase = () => setCount(count + 1);
|
|
717
|
+
const decrease = () => setCount(count - 1);
|
|
718
|
+
|
|
719
|
+
return (
|
|
720
|
+
//one context is gonna share the edition of the state
|
|
721
|
+
<CountContext.Provider value={{ count }}>
|
|
722
|
+
{/* this second component will share the mutations of the state */}
|
|
723
|
+
<CountContextSetter.Provider value={{ increase, decrease }}>
|
|
724
|
+
{children}
|
|
725
|
+
</CountContextSetter.Provider>
|
|
726
|
+
</CountContext.Provider>
|
|
727
|
+
);
|
|
728
|
+
};
|
|
729
|
+
|
|
730
|
+
// 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**
|
|
731
|
+
const App = () => {
|
|
732
|
+
return (
|
|
733
|
+
<UserProvider>
|
|
734
|
+
<CountProvider>
|
|
735
|
+
{/* lets create two componets instead of one */}
|
|
736
|
+
<ComponentSetter />
|
|
737
|
+
<Component />
|
|
738
|
+
</CountProvider>
|
|
739
|
+
</UserProvider>
|
|
740
|
+
);
|
|
741
|
+
};
|
|
742
|
+
|
|
743
|
+
const ComponentSetter = () => {
|
|
744
|
+
const { increase, decrease } = useContext(CountContextSetter);
|
|
745
|
+
|
|
746
|
+
return (
|
|
747
|
+
<View>
|
|
748
|
+
<Button title='Increase' onPress={increase} />
|
|
749
|
+
<Button title='Decrease' onPress={decrease} />
|
|
750
|
+
</View>
|
|
751
|
+
);
|
|
752
|
+
};
|
|
753
|
+
|
|
754
|
+
const Component = () => {
|
|
755
|
+
const { currentUser } = useContext(UserContext);
|
|
756
|
+
|
|
757
|
+
// finally we are able to get access to the new context...
|
|
758
|
+
const { count } = useContext(CountContext);
|
|
759
|
+
|
|
760
|
+
return (
|
|
761
|
+
<View>
|
|
762
|
+
<Text>{currentUser.name}</Text>
|
|
763
|
+
<Text>{count}</Text>
|
|
764
|
+
</View>
|
|
765
|
+
);
|
|
766
|
+
};
|
|
767
|
+
```
|
|
768
|
+
|
|
769
|
+
Wow, a lot!!! just to be able to separate the mutations... and have mutations!!
|
|
770
|
+
|
|
771
|
+
### it would be easier with the GlobalStore? Let's see.
|
|
772
|
+
|
|
773
|
+
```tsx
|
|
774
|
+
type TUser = {
|
|
775
|
+
name: string;
|
|
776
|
+
email: string;
|
|
777
|
+
};
|
|
778
|
+
|
|
779
|
+
const useUser = new GlobalStore<TUser>({
|
|
780
|
+
name: null,
|
|
781
|
+
email: null,
|
|
782
|
+
}).getHook();
|
|
783
|
+
|
|
784
|
+
// let's modify the store to add custom actions, the second parameter is configuration let's just pass null for now
|
|
785
|
+
const countStore = new GlobalStore(0, null, {
|
|
786
|
+
increase() {
|
|
787
|
+
return ({ setState }: StoreTools<number>) => {
|
|
788
|
+
setState((state) => state + 1);
|
|
789
|
+
};
|
|
790
|
+
},
|
|
791
|
+
|
|
792
|
+
decrease() {
|
|
793
|
+
return ({ setState }: StoreTools<number>) => {
|
|
794
|
+
setState((state) => state - 1);
|
|
795
|
+
};
|
|
796
|
+
},
|
|
797
|
+
} as const);
|
|
798
|
+
|
|
799
|
+
const useCount = countStore.getHook();
|
|
800
|
+
|
|
801
|
+
// this actions don't use hooks, but are connected to the store and all the subscribers will be notified
|
|
802
|
+
const [, countActions] = countStore.getHookDecoupled();
|
|
803
|
+
|
|
804
|
+
// this component is not subscribed to the store, so it will not be notified when the state changes
|
|
805
|
+
const ComponentSetter = () => {
|
|
806
|
+
return (
|
|
807
|
+
<View>
|
|
808
|
+
<Button title='Increase' onPress={countActions.increase} />
|
|
809
|
+
<Button title='Decrease' onPress={countActions.decrease} />
|
|
810
|
+
</View>
|
|
811
|
+
);
|
|
812
|
+
};
|
|
813
|
+
|
|
814
|
+
// this component is subscribed to the store, so it will be notified when the state changes
|
|
815
|
+
const Component = () => {
|
|
816
|
+
const [user] = useUser();
|
|
817
|
+
const [count, actions] = useCount();
|
|
818
|
+
|
|
819
|
+
return (
|
|
820
|
+
<View>
|
|
821
|
+
<Text>{count}</Text>
|
|
822
|
+
</View>
|
|
823
|
+
);
|
|
824
|
+
};
|
|
825
|
+
```
|
|
826
|
+
|
|
827
|
+
### So let's analyze what happened
|
|
828
|
+
|
|
829
|
+
To restrict the state manipulations with the custom actions, we just need to add a third parameter to the store.
|
|
830
|
+
|
|
831
|
+
```ts
|
|
832
|
+
const countStore = new GlobalStore(0, null, {
|
|
833
|
+
log: (action: string) => () => console.log(action),
|
|
834
|
+
|
|
835
|
+
// every action is a function that returns a function that receives the store tools
|
|
836
|
+
increase() {
|
|
837
|
+
return ({ setState, getState }: StoreTools<number>): number => {
|
|
838
|
+
setState((state) => state + 1);
|
|
839
|
+
|
|
840
|
+
// actions are able to communicate between them
|
|
841
|
+
this.log('increase');
|
|
842
|
+
|
|
843
|
+
return getState();
|
|
844
|
+
};
|
|
845
|
+
},
|
|
846
|
+
} as const);
|
|
847
|
+
|
|
848
|
+
// the const is necessary to avoid typescript errors
|
|
849
|
+
```
|
|
850
|
+
|
|
851
|
+
All the library is strongly typed, we use generics to return the correct data type in each action.
|
|
852
|
+
|
|
853
|
+
```ts
|
|
854
|
+
const [, actions] = countStore.getHookDecoupled();
|
|
855
|
+
|
|
856
|
+
// for example the type of actions.increase will be: () => number
|
|
857
|
+
// just in case, even the parameters of the actions are gonna be exposed through TS
|
|
858
|
+
```
|
|
859
|
+
|
|
860
|
+
## getHookDecoupled
|
|
861
|
+
|
|
862
|
+
### **getHookDecoupled** returns a tuple with the state and the actions,
|
|
863
|
+
|
|
864
|
+
This is so useful when you want to use the actions without having to be subscribed to changes of the state.
|
|
865
|
+
There is also a third element in the tuple which is a function for getting the metadata of the store
|
|
866
|
+
|
|
867
|
+
### the metadata of the store is not reactive information which could be shared through the store
|
|
868
|
+
|
|
869
|
+
## Adding metadata to the store
|
|
870
|
+
|
|
871
|
+
```tsx
|
|
872
|
+
const [, , getMetadata] = new GlobalStore(0, {
|
|
873
|
+
metadata: {
|
|
874
|
+
isStoredSyncronized: false,
|
|
875
|
+
},
|
|
876
|
+
}).getHookDecoupled();
|
|
877
|
+
|
|
878
|
+
console.log(getMetadata().isStoredSyncronized); // false
|
|
879
|
+
```
|
|
880
|
+
|
|
881
|
+
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
|
|
882
|
+
|
|
883
|
+
...
|
|
884
|
+
|
|
885
|
+
...
|
|
461
886
|
|
|
462
|
-
|
|
887
|
+
# That's it for now!! hope you enjoy coding!!
|
package/lib/GlobalStore.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GlobalStore.d.ts","sourceRoot":"","sources":["../src/GlobalStore.ts"],"names":[],"mappings":"AAAA,cAAc,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"GlobalStore.d.ts","sourceRoot":"","sources":["../src/GlobalStore.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-global-state-hooks",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.10",
|
|
4
4
|
"description": "This is a package to easily handling global-state across your react-native-components No-redux",
|
|
5
5
|
"main": "lib/GlobalStore.js",
|
|
6
6
|
"files": [
|
|
@@ -65,6 +65,6 @@
|
|
|
65
65
|
"react": "workspace:*"
|
|
66
66
|
},
|
|
67
67
|
"dependencies": {
|
|
68
|
-
"json-storage-formatter": "^1.0.
|
|
68
|
+
"json-storage-formatter": "^1.0.5"
|
|
69
69
|
}
|
|
70
70
|
}
|