react-native-global-state-hooks 2.0.7 → 2.1.1
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 +324 -60
- package/lib/GlobalStore.d.ts +160 -48
- package/lib/GlobalStore.d.ts.map +1 -1
- package/lib/GlobalStore.js +275 -134
- package/lib/GlobalStore.types.d.ts +108 -0
- package/lib/GlobalStore.types.d.ts.map +1 -0
- package/lib/{GlobalStoreTypes.js → GlobalStore.types.js} +0 -0
- package/lib/GlobalStore.utils.d.ts +3 -0
- package/lib/GlobalStore.utils.d.ts.map +1 -0
- package/lib/GlobalStore.utils.js +5 -0
- package/lib/index.d.ts +4 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +6 -0
- package/package.json +4 -5
- package/lib/GlobalStoreTypes.d.ts +0 -66
- package/lib/GlobalStoreTypes.d.ts.map +0 -1
package/README.md
CHANGED
|
@@ -1,29 +1,52 @@
|
|
|
1
1
|
# react-native-global-state-hooks
|
|
2
|
-
This is a package to easily handling global-state across your react-native-components **No-redux**, **No-context**
|
|
3
2
|
|
|
4
|
-
This
|
|
3
|
+
This is a package to easily handling global-state across your react-native-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
|
+
...
|
|
8
|
+
|
|
9
|
+
...
|
|
10
|
+
|
|
11
|
+
# Creating a global store
|
|
9
12
|
|
|
10
13
|
We are gonna create a global count example **useCountGlobal.ts**:
|
|
11
14
|
|
|
12
|
-
```
|
|
13
|
-
|
|
14
|
-
import GlobalStore from 'react-native-global-state-hooks';
|
|
15
|
+
```ts
|
|
16
|
+
import { GlobalStore } from 'react-native-global-state-hooks';
|
|
15
17
|
|
|
16
18
|
// initialize your store with the default value of the same.
|
|
17
19
|
const countStore = new GlobalStore(0);
|
|
18
20
|
|
|
19
|
-
//
|
|
21
|
+
// get the hook
|
|
20
22
|
export const useCountGlobal = countStore.getHook();
|
|
21
23
|
|
|
24
|
+
// inside your component just call...
|
|
25
|
+
const [count, setCount] = useCountGlobal(); // no paremeters are needed since this is a global store
|
|
26
|
+
|
|
22
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;
|
|
23
41
|
```
|
|
24
42
|
|
|
25
|
-
|
|
26
|
-
|
|
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:
|
|
27
50
|
|
|
28
51
|
```JSX
|
|
29
52
|
import { useCountGlobal } from './useCountGlobal'
|
|
@@ -37,59 +60,34 @@ const MyFirstComponent: React.FC = () => {
|
|
|
37
60
|
|
|
38
61
|
const MySecondComponent: React.FC = () => {
|
|
39
62
|
const [count, setter] = useCountGlobal();
|
|
40
|
-
|
|
41
|
-
|
|
63
|
+
|
|
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
|
+
|
|
42
67
|
return (<Button title={`count: ${count}`} onPress={onClickAddOne} />);
|
|
43
68
|
}
|
|
44
69
|
|
|
45
|
-
//
|
|
70
|
+
// It's so simple to share information between components
|
|
46
71
|
```
|
|
47
72
|
|
|
48
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:
|
|
49
74
|
|
|
50
|
-
```
|
|
75
|
+
```ts
|
|
51
76
|
const countStore = new GlobalStore(0);
|
|
52
77
|
```
|
|
53
78
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
You could persist the state with **@react-native-async-storage** by just adding the **storage-key** to the constructor of your global-store, for example:
|
|
57
|
-
|
|
58
|
-
```JSX
|
|
59
|
-
// The FIRST parameter is the initial value of the state
|
|
60
|
-
// The Second parameter is an API to restrict access to the state, will talk about that later on [README]:./README.advance.md
|
|
61
|
-
// The Third parameter is the key that will be used on the async-storage
|
|
62
|
-
const countStore = new GlobalStore(0, null, 'GLOBAL_COUNT');
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
## Consuming Persisted Store
|
|
66
|
-
|
|
67
|
-
```JSX
|
|
68
|
-
const MyComponent: React.FC = () => {
|
|
69
|
-
// connect the component to the global persisted storage
|
|
70
|
-
const [count, setCount, isCountReady] = useCountGlobal();
|
|
71
|
-
const onClickAddOne = () => setCount(count + 1);
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* since the async storage is ASYNC, the first time the hook is called into a component we may get the default value, instead of the one from the storage.
|
|
75
|
-
*
|
|
76
|
-
* We are able to validate this with the third value returned in the hook array **isCountReady**, which is a boolean which let us now if the async storage already was reached out.
|
|
77
|
-
*/
|
|
78
|
-
const countLabel = isCountReady ? `count: ${count}` : 'Loading async storage...';
|
|
79
|
+
...
|
|
79
80
|
|
|
80
|
-
|
|
81
|
-
}
|
|
82
|
-
```
|
|
81
|
+
...
|
|
83
82
|
|
|
84
|
-
|
|
83
|
+
# Decoupled hook
|
|
85
84
|
|
|
86
|
-
If you want to access the global state outside a component or outside a hook, or without subscribing the component to the state changes...
|
|
85
|
+
If you want to access the global state outside a component or outside a hook, or without subscribing the component to the state changes...
|
|
87
86
|
|
|
88
|
-
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.
|
|
87
|
+
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.
|
|
89
88
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
import GlobalStore from 'react-native-global-state-hooks';
|
|
89
|
+
```ts
|
|
90
|
+
import { GlobalStore } from 'react-native-global-state-hooks';
|
|
93
91
|
|
|
94
92
|
const countStore = new GlobalStore(0);
|
|
95
93
|
|
|
@@ -97,14 +95,15 @@ const countStore = new GlobalStore(0);
|
|
|
97
95
|
export const useCountGlobal = countStore.getHook();
|
|
98
96
|
|
|
99
97
|
// this functions are not hooks, and they can be used in whatever place into your code, ClassComponents, OtherHooks, Services etc.
|
|
100
|
-
export const [
|
|
101
|
-
|
|
98
|
+
export const [getCount, sendCount] = countStore.getHookDecoupled();
|
|
102
99
|
```
|
|
103
100
|
|
|
104
|
-
Let's see a trivial example:
|
|
101
|
+
Let's see a trivial example:
|
|
102
|
+
|
|
103
|
+
...
|
|
105
104
|
|
|
106
105
|
```JSX
|
|
107
|
-
import { useCountGlobal,
|
|
106
|
+
import { useCountGlobal, sendCount } from './useCountGlobal'
|
|
108
107
|
|
|
109
108
|
const CountDisplayerComponent: React.FC = () => {
|
|
110
109
|
const [count] = useCountGlobal();
|
|
@@ -112,10 +111,12 @@ const CountDisplayerComponent: React.FC = () => {
|
|
|
112
111
|
return (<Text>{count}<Text/>);
|
|
113
112
|
}
|
|
114
113
|
|
|
114
|
+
// here we have a separate component that is gonna handle the state of the previous component we created,
|
|
115
|
+
// this new component is not gonna be affected by the changes applied on <CountDisplayerComponent/>
|
|
115
116
|
// Stage2 does not need to be updated once the global count changes
|
|
116
117
|
const CountManagerComponent: React.FC = () => {
|
|
117
|
-
const increaseClick = () =>
|
|
118
|
-
const decreaseClick = () =>
|
|
118
|
+
const increaseClick = useCallback(() => sendCount(count => count + 1), []);
|
|
119
|
+
const decreaseClick = useCallback(() => sendCount(count => count - 1), []);
|
|
119
120
|
|
|
120
121
|
return (<>
|
|
121
122
|
<Button onPress={increaseClick} title={'increase'} />
|
|
@@ -124,18 +125,281 @@ const CountManagerComponent: React.FC = () => {
|
|
|
124
125
|
}
|
|
125
126
|
```
|
|
126
127
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
128
|
+
...
|
|
129
|
+
|
|
130
|
+
...
|
|
131
|
+
|
|
132
|
+
# Extending the global storage
|
|
133
|
+
|
|
134
|
+
Implementing extra functionality to extend the capabilities of the GlobalStorage couldn't be easier!!!
|
|
135
|
+
|
|
136
|
+
Here is an example of how you could create your custom store that for example stores the state into a async-storage persistente...
|
|
137
|
+
|
|
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
|
+
|
|
140
|
+
```ts
|
|
141
|
+
export class GlobalStoreAsync<
|
|
142
|
+
TState,
|
|
143
|
+
TMetadata extends { readonly isAsyncStorageReady: boolean },
|
|
144
|
+
TStateSetter extends
|
|
145
|
+
| ActionCollectionConfig<TState, TMetadata>
|
|
146
|
+
| StateSetter<TState>
|
|
147
|
+
| null = StateSetter<TState>
|
|
148
|
+
> extends GlobalStore<TState, TMetadata, TStateSetter> {
|
|
149
|
+
protected isAsyncStorageReady: boolean = false;
|
|
150
|
+
|
|
151
|
+
protected config: GlobalStoreConfig<
|
|
152
|
+
TState,
|
|
153
|
+
TMetadata,
|
|
154
|
+
NonNullable<TStateSetter>
|
|
155
|
+
> & {
|
|
156
|
+
asyncStorageKey: string; // key of the async storage
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
constructor(
|
|
160
|
+
state: TState,
|
|
161
|
+
metadata: TMetadata = { isAsyncStorageReady: false } as TMetadata,
|
|
162
|
+
setterConfig: TStateSetter | null = null,
|
|
163
|
+
{
|
|
164
|
+
onInit: onInitConfig,
|
|
165
|
+
...config
|
|
166
|
+
}: GlobalStoreConfig<TState, TMetadata, NonNullable<TStateSetter>> & {
|
|
167
|
+
asyncStorageKey: string; // key of the async storage
|
|
168
|
+
}
|
|
169
|
+
) {
|
|
170
|
+
super(state, metadata, setterConfig, config);
|
|
171
|
+
|
|
172
|
+
const parameters = this.getConfigCallbackParam({});
|
|
173
|
+
|
|
174
|
+
this.onInit(parameters);
|
|
175
|
+
onInitConfig?.(parameters);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* This method will be called once the store is created after the constructor,
|
|
180
|
+
* this method is different from the onInit of the confg property and it won't be overriden
|
|
181
|
+
*/
|
|
182
|
+
protected onInit = async ({
|
|
183
|
+
setState,
|
|
184
|
+
setMetadata,
|
|
185
|
+
getMetadata,
|
|
186
|
+
}: StateConfigCallbackParam<
|
|
187
|
+
TState,
|
|
188
|
+
TMetadata,
|
|
189
|
+
NonNullable<TStateSetter>
|
|
190
|
+
>) => {
|
|
191
|
+
const { asyncStorageKey } = this.config;
|
|
192
|
+
const storedItem: string = await asyncStorage.getItem(asyncStorageKey);
|
|
193
|
+
|
|
194
|
+
this.isAsyncStorageReady = true;
|
|
195
|
+
|
|
196
|
+
setMetadata({
|
|
197
|
+
...getMetadata(),
|
|
198
|
+
isAsyncStorageReady: true,
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
if (!storedItem) return;
|
|
202
|
+
|
|
203
|
+
const jsonParsed = JSON.parse(storedItem);
|
|
204
|
+
const items = formatFromStore<TState>(jsonParsed);
|
|
205
|
+
|
|
206
|
+
setState(items);
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
protected onStateChanged = ({
|
|
210
|
+
getState,
|
|
211
|
+
}: StateChangesParam<TState, TMetadata, NonNullable<TStateSetter>>) => {
|
|
212
|
+
const state = getState();
|
|
213
|
+
const formattedObject = formatToStore(state);
|
|
214
|
+
const jsonValue = JSON.stringify(formattedObject);
|
|
215
|
+
const { asyncStorageKey } = this.config;
|
|
216
|
+
|
|
217
|
+
asyncStorage.setItem(asyncStorageKey, jsonValue);
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
The methods **formatToStore** and **formatFromStore** are part of another library of my [json-storage-formatter](https://www.npmjs.com/package/json-storage-formatter)...
|
|
223
|
+
|
|
224
|
+
this super small library will help you to transform objects into json string and get them back without losing any of the original data types... no more problems when **stringify** Dates, Maps, Sets, etc...
|
|
225
|
+
|
|
226
|
+
...
|
|
227
|
+
|
|
228
|
+
Originally the library was implementing persistent storage by using the package **@react-native-async-storage**, but not all people want to use it or need to use it... so it has been removed. Feel free to use the above example to get back that functionality if you were using the previous versions of the package.
|
|
229
|
+
|
|
230
|
+
You could find this example on the [**GitHub** of the project](https://github.com/johnny-quesada-developer/json-storage-formatter), between the unit test sources when the case scenario was tested [GlobalStoreAsyc.ts](https://github.com/johnny-quesada-developer/react-native-global-state-hooks/blob/master/%40tests/__test__/GlobalStoreAsyc.ts)
|
|
231
|
+
|
|
232
|
+
...
|
|
233
|
+
|
|
234
|
+
...
|
|
235
|
+
|
|
236
|
+
# Restricting the manipulation of the global **state**
|
|
237
|
+
|
|
238
|
+
## Who hate reducers?
|
|
239
|
+
|
|
240
|
+
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**
|
|
241
|
+
...
|
|
242
|
+
|
|
243
|
+
```ts
|
|
244
|
+
const initialValue = 0;
|
|
245
|
+
|
|
246
|
+
// this is not reactive information that you could also store in the async storage
|
|
247
|
+
// upating the metadata will not trigger the onStateChanged method or any update on the components
|
|
248
|
+
const metadata = null;
|
|
249
|
+
|
|
250
|
+
const countStore = new GlobalStore(
|
|
251
|
+
initialValue,
|
|
252
|
+
metadata,
|
|
253
|
+
{
|
|
254
|
+
log: (message: string) => (): void => {
|
|
255
|
+
console.log(message);
|
|
256
|
+
},
|
|
257
|
+
|
|
258
|
+
increase(message: string) {
|
|
259
|
+
return (storeTools: StoreTools<number>) => {
|
|
260
|
+
this.log(message);
|
|
261
|
+
|
|
262
|
+
return storeTools.getState();
|
|
263
|
+
};
|
|
264
|
+
},
|
|
265
|
+
|
|
266
|
+
decrease(message: string) {
|
|
267
|
+
return (storeTools: StoreTools<number>) => {
|
|
268
|
+
this.log(message);
|
|
269
|
+
|
|
270
|
+
return storeTools.getState();
|
|
271
|
+
};
|
|
272
|
+
},
|
|
273
|
+
} as const // the -as const- is necessary to avoid typescript errors
|
|
274
|
+
);
|
|
275
|
+
|
|
276
|
+
// the way to get the hook is the same as for simple setters
|
|
277
|
+
const useCountStore = countStore.getHook();
|
|
278
|
+
|
|
279
|
+
// now instead of a setState method, you'll get an actions object
|
|
280
|
+
// that contains all the actions that you defined in the setterConfig
|
|
281
|
+
const [count, countActions] = useCountStore();
|
|
282
|
+
|
|
283
|
+
// count is the current state - 0 (number)
|
|
284
|
+
// countActions is an object that contains all the actions that you defined in the setterConfig
|
|
285
|
+
// countActions.increase(); // this will increase the count by 1, returns the new count (number)
|
|
286
|
+
// countActions.decrease(); // this will decrease the count by 1, returns the new count (number)
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
...
|
|
290
|
+
|
|
291
|
+
# Configuration callbacks
|
|
292
|
+
|
|
293
|
+
## config.onInit
|
|
294
|
+
|
|
295
|
+
This method will be called once the store is created after the constructor,
|
|
296
|
+
|
|
297
|
+
@examples
|
|
298
|
+
|
|
299
|
+
```ts
|
|
300
|
+
import { GlobalStore } from 'react-native-global-state-hooks';
|
|
301
|
+
|
|
302
|
+
const initialValue = 0;
|
|
303
|
+
|
|
304
|
+
const store = new GlobalStore(0, null, null, {
|
|
305
|
+
onInit: async ({ setMetadata, setState }) => {
|
|
306
|
+
const data = await someApiCall();
|
|
307
|
+
|
|
308
|
+
setState(data);
|
|
309
|
+
setMetadata({ isDataUpdated: true });
|
|
310
|
+
},
|
|
311
|
+
});
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
...
|
|
315
|
+
|
|
316
|
+
## config.onStateChanged
|
|
317
|
+
|
|
318
|
+
This method will be called every time the state is changed
|
|
319
|
+
|
|
320
|
+
@examples
|
|
321
|
+
|
|
322
|
+
```ts
|
|
323
|
+
import { GlobalStore } from 'react-native-global-state-hooks';
|
|
324
|
+
|
|
325
|
+
const store = new GlobalStore(0, null, null, {
|
|
326
|
+
onStateChanged: ({ getState }) => {
|
|
327
|
+
const state = getState();
|
|
328
|
+
|
|
329
|
+
console.log(state);
|
|
330
|
+
},
|
|
331
|
+
});
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
...
|
|
335
|
+
|
|
336
|
+
## config.onSubscribed
|
|
337
|
+
|
|
338
|
+
This method will be called every time a component is subscribed to the store
|
|
339
|
+
|
|
340
|
+
@examples
|
|
341
|
+
|
|
342
|
+
```ts
|
|
343
|
+
import { GlobalStore } from 'react-native-global-state-hooks';
|
|
344
|
+
|
|
345
|
+
const store = new GlobalStore(0, null, null, {
|
|
346
|
+
onSubscribed: ({ getState }) => {
|
|
347
|
+
console.log('A component was subscribed to the store');
|
|
348
|
+
},
|
|
349
|
+
});
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
...
|
|
353
|
+
|
|
354
|
+
## config.computePreventStateChange
|
|
355
|
+
|
|
356
|
+
This method will be called every time the state is going to be changed, if it returns true the state won't be changed
|
|
357
|
+
|
|
358
|
+
@examples
|
|
359
|
+
|
|
360
|
+
```ts
|
|
361
|
+
import { GlobalStore } from 'react-native-global-state-hooks';
|
|
362
|
+
|
|
363
|
+
const store = new GlobalStore(0, null, null, {
|
|
364
|
+
computePreventStateChange: ({ getState }) => {
|
|
365
|
+
const state = getState();
|
|
366
|
+
const shouldPrevent = state < 0;
|
|
367
|
+
|
|
368
|
+
if (shouldPrevent) return true;
|
|
369
|
+
|
|
370
|
+
return false;
|
|
371
|
+
},
|
|
372
|
+
});
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
...
|
|
376
|
+
|
|
377
|
+
...
|
|
378
|
+
|
|
379
|
+
...
|
|
380
|
+
|
|
381
|
+
...
|
|
382
|
+
|
|
383
|
+
...
|
|
130
384
|
|
|
131
385
|
## Advantages:
|
|
386
|
+
|
|
132
387
|
1. Using REACT's simplest and default way to deal with the state.
|
|
133
388
|
2. Adding partial state designations (This is not on useState default functionality)
|
|
134
389
|
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.
|
|
135
390
|
4. This library is already taking care of avoiding re-renders if the new state does not have changes
|
|
136
|
-
5. This tool also take care for you to avoid **async-storage
|
|
391
|
+
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
|
|
392
|
+
|
|
393
|
+
...
|
|
394
|
+
|
|
395
|
+
...
|
|
396
|
+
|
|
397
|
+
...
|
|
398
|
+
|
|
399
|
+
...
|
|
137
400
|
|
|
138
401
|
# Finallly notes:
|
|
139
|
-
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**...
|
|
140
402
|
|
|
141
|
-
|
|
403
|
+
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**...
|
|
404
|
+
|
|
405
|
+
This utility is just including the implementation of the use state into a subscriber pattern, to enable you to create hooks that will be subscribed to specific store changes, does how we'll be creating a global state hook.
|