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 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 utility follows the same style as the default **useState** hook, with a subscription pattern and **HOFs** to create a more intuitive, atomic and easy way of sharing state between components
3
+ This is a package to easily handling global-state across your react-native-components
5
4
 
6
- **after version 1.0.4, we migrated to @react-native-async-storage/async-storage, because @react-native-async-storage/async-storage has been deprecated!!**
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
- ## Creating a global store, an a simple hook
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
- ```JSX
13
- // Import the store costructor
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
- // you'll use this function the same way you'll use the **useState**
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
- ## Implementing your global hook into your components
26
- Let's say we have two components **MyFirstComponent**, **MySecondComponent**, in order to use our global hook they will look just like:
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
- const onClickAddTwo = () => setter(count + 2);
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
- // Just like that! You are now using a global state!!
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
- ```JSX
75
+ ```ts
51
76
  const countStore = new GlobalStore(0);
52
77
  ```
53
78
 
54
- ## Persisted store
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
- return (<Button title={countLabel} onPress={onClickAddOne} />);
81
- }
82
- ```
81
+ ...
83
82
 
84
- ## Decoupled hook
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
- ```JSX
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 [getCountGlobalValue, setCountGlobalValue] = countStore.getHookDecoupled();
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, setCountGlobalValue } from './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 = () => setCountGlobalValue(count => count + 1);
118
- const decreaseClick = () => setCountGlobalValue(count => count - 1);
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
- ## Advance Config
128
- Here you can see more information how to create more complex services for your global stores.
129
- [README]:./README.advance.md
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*** data to lose the data types that you stored. For example when you are using datetimes
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
- 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.
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.