react-native-global-state-hooks 3.0.7 → 3.0.8

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,168 +1,342 @@
1
1
  # react-native-global-state-hooks
2
2
 
3
- This is a package to easily handling global-state across your react-native-components
4
-
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... You can see the introduction video [here!](https://www.youtube.com/watch?v=WfoMhO1zZ04&t=8s)
6
-
7
- To see TODO-LIST with the hooks and async storage take a look here: [todo-list-with-global-hooks](https://github.com/johnny-quesada-developer/todo-list-with-global-hooks.git)
3
+ This is a package to easily handling **global state hooks** across your **react components**
8
4
 
9
5
  For seen a running example of the hooks, you can check the following link: [react-global-state-hooks-example](https://johnny-quesada-developer.github.io/react-global-state-hooks-example/)
10
6
 
11
- ...
7
+ To see **TODO-LIST** with the hooks and **async storage** take a look here: [todo-list-with-global-hooks](https://github.com/johnny-quesada-developer/todo-list-with-global-hooks.git).
8
+
9
+ To see how to create a custom hook connected to your favorite async storage, please refer to the documentation section titled **Extending Global Hooks**
12
10
 
13
- ...
11
+ You can also see an introduction video [here!](https://www.youtube.com/watch?v=WfoMhO1zZ04&t=8s)
14
12
 
15
- # Creating a global store
13
+ # Creating a global state
16
14
 
17
- We are gonna create a global count example **useCountGlobal.ts**:
15
+ We are gonna create a global state hook **useCount** with one line of code.
18
16
 
19
17
  ```ts
20
- import { createGlobalHook } from 'react-native-global-state-hooks';
18
+ import { createGlobalState } from 'react-native-global-state-hooks';
19
+
20
+ export const useCount = createGlobalState(0);
21
+ ```
21
22
 
22
- // initialize your store with the default value of the same.
23
- export const useCountGlobal = createGlobalHook(0);
23
+ That's it! Welcome to global hooks. Now, you can use this state wherever you need it in your application.
24
24
 
25
- // inside your component just call...
26
- const [count, setCount] = useCountGlobal(); // no parameters are needed since this is a global store
25
+ Let's see how to use it inside a simple **component**
27
26
 
28
- // That's it, that's a global store... Strongly typed, with a global-hook that we could reuse cross all our react-components.
27
+ ```ts
28
+ const [count, setCount] = useCount();
29
29
 
30
- // #### Optionally you are able to use a decoupled hook,
31
- // #### This function is linked to the store hooks but is not a hook himself.
30
+ return <Button onClick={() => setCount((count) => count + 1)}>{count}</Button>;
31
+ ```
32
32
 
33
- // we could create the store in this way to get extra capabilities
34
- const countStore = new GlobalStore(0);
33
+ Isn't it cool? It works just like a regular **useState**. Notice the only difference is that now you don't need to provide the initial value since this is a global hook, and the initial value has already been provided.
35
34
 
36
- // the useCountGlobal works exactly the same
37
- export const useCountGlobal = countStore.getHook();
35
+ # Decoupled state access
38
36
 
39
- // but now we are also able to get a decoupled hook
40
- export const [getCount, sendCount] = countStore.getHookDecoupled();
37
+ If you need to access the global state outside of a component or a hook without subscribing to state changes, you can use the **createGlobalStateWithDecoupledFuncs**.
41
38
 
42
- // @example
43
- console.log(getCount()); // 0;
39
+ Decoupled state access is particularly useful when you want to create components that have editing access to a specific store but don't necessarily need to reactively respond to state changes. For example, consider a search component that only needs to retrieve the current state whenever it performs a data search. It doesn't require continuous subscription to changes in the collection it filters.
44
40
 
45
- // components subscribed to the global hook if there are so
46
- sendCount(5);
41
+ Using decoupled state access allows you to retrieve the state when needed without establishing a reactive relationship with the state changes. This approach provides more flexibility and control over when and how components interact with the global state. Let's see and example:
47
42
 
48
- console.log(getCount()); // 5;
43
+ ```ts
44
+ import { createGlobalStateWithDecoupledFuncs } from 'react-native-global-state-hooks';
45
+
46
+ export const [useCount, getCount, setCount] =
47
+ createGlobalStateWithDecoupledFuncs(0);
49
48
  ```
50
49
 
51
- ...
50
+ That's great! With the addition of the **getCount** and **setCount** methods, you now have the ability to access and modify the state without the need for subscription to the hook.
52
51
 
53
- ...
52
+ By using the **getCount** method, you can retrieve the current value of the state stored in **useCount**. This allows you to access the state whenever necessary, without being reactive to its changes.
54
53
 
55
- # Implementing your global hook into your components
54
+ Similarly, the **setCount** method enables you to modify the state stored in **useCount**. You can use this method to update the state with a new value or perform any necessary state mutations.
56
55
 
57
- Let's say we have two components **MyFirstComponent**, **MySecondComponent**, in order to use our global hook they will look just like:
56
+ These additional methods provide a more flexible and granular way to interact with the state managed by **useCount**. You can retrieve and modify the state as needed, without establishing a subscription relationship or reactivity with the state changes.
58
57
 
59
- ```JSX
60
- import { useCountGlobal } from './useCountGlobal'
58
+ # State actions
61
59
 
62
- const MyFirstComponent: React.FC = () => {
63
- const [count, setter] = useCountGlobal();
64
- const onClickAddOne = () => setter(count + 1);
60
+ Is common and often necessary to restrict the manipulation of state to a specific set of actions or operations. To achieve this, we can simplify the process by adding a custom API to the configuration of our **global state**.
65
61
 
66
- return (<Button title={`count: ${count}`} onPress={onClickAddOne} />);
67
- }
62
+ By defining a custom API for the **useCount**, we can encapsulate and expose only the necessary actions or operations that are allowed to modify the state. This provides a controlled interface for interacting with the state, ensuring that modifications adhere to the desired restrictions.
68
63
 
69
- const MySecondComponent: React.FC = () => {
70
- const [count, setter] = useCountGlobal();
64
+ Let's see and example (you can use **createGlobalStateWithDecoupledFuncs** or **createGlobalState**, but methods work the the same)
71
65
 
72
- // it can also be use as a normal setter into a callback or other hooks
73
- const onClickAddTwo = useCallback(() => setter(state => state + 2), [])
66
+ ```ts
67
+ import { createGlobalState } from 'react-native-global-state-hooks';
74
68
 
75
- return (<Button title={`count: ${count}`} onPress={onClickAddOne} />);
76
- }
69
+ export const useCount = createGlobalState(0, {
70
+ actions: {
71
+ increase(value: number = 1) {
72
+ return ({ getState }: StoreTools<number>) => {
73
+ setState((count) => count + value);
74
+ };
75
+ },
76
+ },
77
+ });
78
+ ```
79
+
80
+ That's great! In this updated version, the **useCount** hook will no longer return [**state**, **stateSetter**] but instead will return [**state**, **actions**]. This change will provide a more intuitive and convenient way to access and interact with the state and its associated actions.
77
81
 
78
- // It's so simple to share information between components
82
+ Let's see how that will look into a react component:
83
+
84
+ ```ts
85
+ const [count, actions] = useCount();
86
+
87
+ // Every time we click, the counter will increase by 2
88
+ return <Button onClick={() => actions.increase(2)}>{count}</Button>;
79
89
  ```
80
90
 
81
- 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:
91
+ Let's add more actions to the state and explore how to use one action from inside another.
92
+
93
+ Here's an example of adding multiple actions to the state and utilizing one action within another:
82
94
 
83
95
  ```ts
84
- const countStore = new GlobalStore(0);
96
+ import { createGlobalState } from 'react-native-global-state-hooks';
97
+
98
+ export const useCount = createGlobalState(0, {
99
+ actions: {
100
+ log: (currentValue: string) => {
101
+ return ({ getState }: StoreTools<number>): void => {
102
+ console.log(`Current Value: ${getState()}`);
103
+ };
104
+ },
105
+
106
+ increase(value: number = 1) {
107
+ return ({ getState, setState, actions }: StoreTools<number>) => {
108
+ setState((count) => count + value);
109
+
110
+ actions.log(message);
111
+ };
112
+ },
113
+
114
+ decrease(value: number = 1) {
115
+ return ({ getState, setState, actions }: StoreTools<number>) => {
116
+ setState((count) => count - value);
117
+
118
+ actions.log(message);
119
+ };
120
+ },
121
+ },
122
+ });
85
123
  ```
86
124
 
87
- ...
125
+ Notice that the **StoreTools** will contain a reference to the generated actions API. From there, you'll be able to access all actions from inside another one... the **StoreTools** is generic and allow your to set an interface for getting the typing on the actions.
88
126
 
89
- ...
127
+ If you don't want to create an extra type please use **createGlobalStateWithDecoupledFuncs** in that way you'll be able to use the decoupled **actions** which will have the correct typing. Let's take a quick look into that:
128
+
129
+ ```ts
130
+ import { createGlobalStateWithDecoupledFuncs } from 'react-native-global-state-hooks';
131
+
132
+ export const [useCount, getCount, $actions] =
133
+ createGlobalStateWithDecoupledFuncs(0, {
134
+ actions: {
135
+ log: (currentValue: string) => {
136
+ return ({ getState }: StoreTools<number>): void => {
137
+ console.log(`Current Value: ${getState()}`);
138
+ };
139
+ },
140
+
141
+ increase(value: number = 1) {
142
+ return ({ getState, setState }: StoreTools<number>) => {
143
+ setState((count) => count + value);
144
+
145
+ $actions.log(message);
146
+ };
147
+ },
148
+ },
149
+ });
150
+ ```
90
151
 
91
- # Decoupled hook
152
+ In the example the hook will work the same and you'll have access to the correct typing.
92
153
 
93
- If you want to access the global state outside a component or outside a hook, or without subscribing the component to the state changes...
154
+ # Extending Global Hooks
94
155
 
95
- 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.
156
+ Creating a global hook that connects to an asyncStorage is made incredibly easy with the **createCustomGlobalState** function.
157
+
158
+ This function returns a new global state builder wrapped with the desired custom implementation, allowing you to get creative! Le'ts see and example:
96
159
 
97
160
  ```ts
98
- import { GlobalStore } from 'react-native-global-state-hooks';
161
+ import { formatFromStore, formatToStore, createCustomGlobalState } = 'react-native-global-state-hooks'
99
162
 
100
- const countStore = new GlobalStore(0);
163
+ // Optional configuration available for the consumers of the builder
164
+ type HookConfig = {
165
+ asyncStorageKey?: string;
166
+ };
101
167
 
102
- // remember this should be used as the **useState** hook.
103
- export const useCountGlobal = countStore.getHook();
168
+ // This is the base metadata that all the stores created from the builder will have.
169
+ type BaseMetadata = {
170
+ isAsyncStorageReady?: boolean;
171
+ };
104
172
 
105
- // this functions are not hooks, and they can be used in whatever place into your code, ClassComponents, OtherHooks, Services etc.
106
- export const [getCount, sendCount] = countStore.getHookDecoupled();
107
- ```
173
+ export const createGlobalState = createCustomGlobalState<
174
+ BaseMetadata,
175
+ HookConfig
176
+ >({
177
+ /**
178
+ * This function executes immediately after the global state is created, before the invocations of the hook
179
+ */
180
+ onInitialize: async ({ setState, setMetadata }, config) => {
181
+ setMetadata((metadata) => ({
182
+ ...(metadata ?? {}),
183
+ isAsyncStorageReady: null,
184
+ }));
185
+
186
+ const asyncStorageKey = config.asyncStorageKey;
187
+ if (!asyncStorageKey) return;
108
188
 
109
- Let's see a trivial example:
189
+ const storedItem = (await asyncStorage.getItem(asyncStorageKey)) as string;
110
190
 
111
- ...
191
+ // update the metadata, remember, metadata is not reactive
192
+ setMetadata((metadata) => ({
193
+ ...metadata,
194
+ isAsyncStorageReady: true,
195
+ }));
112
196
 
113
- ```JSX
114
- import { useCountGlobal, sendCount } from './useCountGlobal'
197
+ if (storedItem === null) {
198
+ return setState((state) => state, { forceUpdate: true });
199
+ }
115
200
 
116
- const CountDisplayComponent: React.FC = () => {
117
- const [count] = useCountGlobal();
201
+ const parsed = formatFromStore(storedItem, {
202
+ jsonParse: true,
203
+ });
118
204
 
119
- return (<Text>{count}<Text/>);
120
- }
205
+ setState(parsed, { forceUpdate: true });
206
+ },
121
207
 
122
- // here we have a separate component that is gonna handle the state of the previous component we created,
123
- // this new component is not gonna be affected by the changes applied on <CountDisplayComponent/>
124
- // Stage2 does not need to be updated once the global count changes
125
- const CountManagerComponent: React.FC = () => {
126
- const increaseClick = useCallback(() => sendCount(count => count + 1), []);
127
- const decreaseClick = useCallback(() => sendCount(count => count - 1), []);
128
-
129
- return (<>
130
- <Button onPress={increaseClick} title={'increase'} />
131
- <Button onPress={decreaseClick} title={'decrease'}/>
132
- </>);
133
- }
208
+ onChange: ({ getState }, config) => {
209
+ if (!config.asyncStorageKey) return;
210
+
211
+ const state = getState();
212
+
213
+ const formattedObject = formatToStore(state, {
214
+ stringify: true,
215
+ });
216
+
217
+ asyncStorage.setItem(config.asyncStorageKey, formattedObject);
218
+ },
219
+ });
134
220
  ```
135
221
 
136
- ...
222
+ It is important to use **forceUpdate** to force React to re-render our components and obtain the most recent state of the **metadata**. This is especially useful when working with primitive types, as it can be challenging to differentiate between a primitive value that originates from storage and one that does not.
223
+
224
+ It is worth mentioning that the **onInitialize** function will be executed only once per global state.
225
+
226
+ You can use to **formatToStore**, and **formatFromStore** to sanitize your data, These methods will help you transform objects into JSON strings and retrieve them back without losing any of the original data types. You will no longer encounter problems when **stringifying** Dates, Maps, Sets, and other complex data types. You could take a look in the API here: [json-storage-formatter](https://www.npmjs.com/package/json-storage-formatter).
227
+
228
+ Let's see how to create a global state using our new builder:
229
+
230
+ ```ts
231
+ const useTodos = createGlobalState(new Map<string, number>(), {
232
+ config: {
233
+ asyncStorageKey: 'todos',
234
+ },
235
+ });
236
+ ```
237
+
238
+ That's correct! If you add an **asyncStorageKey** to the state configuration, the state will be synchronized with the **asyncStorage**
239
+
240
+ Let's see how to use this async storage hook into our components:
241
+
242
+ ```ts
243
+ const [todos, setTodos, metadata] = useTodos();
244
+
245
+ return (<>
246
+ {metadata.isAsyncStorageReady ? <TodoList todos={todos} /> : <Text>Loading...</Text>}
247
+ <>);
248
+ ```
137
249
 
138
- ...
250
+ The **metadata** is not reactive information and can only be modified from inside the global state lifecycle methods.
139
251
 
140
- # Extending the global storage
252
+ # Life cycle methods
141
253
 
142
- Implementing extra functionality to extend the capabilities of the GlobalStorage couldn't be easier!!!
254
+ There are some lifecycle methods available for use with global hooks, let's review them:
143
255
 
144
- Here is an example of how you could create your custom store that for example stores the state into a async-storage persistent...
256
+ ```ts
257
+ /**
258
+ * @description callback function called when the store is initialized
259
+ * @returns {void} result - void
260
+ * */
261
+ onInit?: ({
262
+ /**
263
+ * Set the metadata
264
+ * @param {TMetadata} setter - The metadata or a function that will receive the metadata and return the new metadata
265
+ * */
266
+ setMetadata: MetadataSetter<TMetadata>;
267
+
268
+ /**
269
+ * Set the state
270
+ * @param {TState} setter - The state or a function that will receive the state and return the new state
271
+ * @param {{ forceUpdate?: boolean }} options - Options
272
+ * */
273
+ setState: StateSetter<TState>;
274
+
275
+ /**
276
+ * Get the state
277
+ * @returns {TState} result - The state
278
+ * */
279
+ getState: () => TState;
280
+
281
+ /**
282
+ * Get the metadata
283
+ * @returns {TMetadata} result - The metadata
284
+ * */
285
+ getMetadata: () => TMetadata;
286
+
287
+ /**
288
+ * Actions of the hook if configuration was provided
289
+ */
290
+ actions: TActions;
291
+ }: StateConfigCallbackParam<TState, TMetadata, TActions>) => void;
292
+
293
+ /**
294
+ * @description - callback function called every time the state is changed
295
+ */
296
+ onStateChanged?: (parameters: StateChangesParam<TState, TMetadata, TActions>) => void;
297
+
298
+ /**
299
+ * callback function called every time a component is subscribed to the store
300
+ */
301
+ onSubscribed?: (parameters: StateConfigCallbackParam<TState, TMetadata, TActions>) => void;
302
+
303
+ /**
304
+ * callback function called every time the state is about to change and it allows you to prevent the state change
305
+ */
306
+ computePreventStateChange?: (parameters: StateChangesParam<TState, TMetadata, TActions>) => boolean;
307
+ ```
145
308
 
146
- 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.
309
+ You can pass this callbacks between on the second parameter of the builders like **createGlobalState**
147
310
 
148
311
  ```ts
149
- import asyncStorage from '@react-native-async-storage/async-storage';
150
- import { formatFromStore, formatToStore } from 'json-storage-formatter';
312
+ const useData = createGlobalState(
313
+ { value: 1 },
314
+ {
315
+ metadata: {
316
+ someExtraInformation: 'someExtraInformation',
317
+ },
318
+ // onSubscribed: (StateConfigCallbackParam) => {},
319
+ // onInit // etc
320
+ computePreventStateChange: ({ state, previousState }) => {
321
+ const prevent = isEqual(state, previousState);
322
+
323
+ return prevent;
324
+ },
325
+ }
326
+ );
327
+ ```
328
+
329
+ Finally, if you have a very specific necessity but still want to use the global hooks, you can extend the **GlobalStoreAbstract** class. This will give you even more control over the state and the lifecycle of the global state.
151
330
 
152
- import {
153
- GlobalStore as GlobalStoreBase,
154
- ActionCollectionConfig,
155
- StateChangesParam,
156
- StateConfigCallbackParam,
157
- StateSetter,
158
- } from 'react-native-global-state-hooks';
331
+ Let's see an example again with the **asyncStorage** custom global hook but with the abstract class.
159
332
 
333
+ ```ts
160
334
  export class GlobalStore<
161
335
  TState,
162
336
  TMetadata extends {
163
337
  asyncStorageKey?: string;
164
338
  isAsyncStorageReady?: boolean;
165
- },
339
+ } | null = null,
166
340
  TStateSetter extends
167
341
  | ActionCollectionConfig<TState, TMetadata>
168
342
  | StateSetter<TState> = StateSetter<TState>
@@ -170,9 +344,9 @@ export class GlobalStore<
170
344
  constructor(
171
345
  state: TState,
172
346
  config: GlobalStoreConfig<TState, TMetadata, TStateSetter> = {},
173
- setterConfig: TStateSetter | null = null
347
+ actionsConfig: TStateSetter | null = null
174
348
  ) {
175
- super(state, config, setterConfig);
349
+ super(state, config, actionsConfig);
176
350
 
177
351
  this.initialize();
178
352
  }
@@ -183,8 +357,13 @@ export class GlobalStore<
183
357
  getMetadata,
184
358
  getState,
185
359
  }: StateConfigCallbackParam<TState, TMetadata, TStateSetter>) => {
360
+ setMetadata({
361
+ ...(metadata ?? {}),
362
+ isAsyncStorageReady: null,
363
+ });
364
+
186
365
  const metadata = getMetadata();
187
- const { asyncStorageKey } = metadata;
366
+ const asyncStorageKey = metadata?.asyncStorageKey;
188
367
 
189
368
  if (!asyncStorageKey) return;
190
369
 
@@ -212,7 +391,7 @@ export class GlobalStore<
212
391
  getMetadata,
213
392
  getState,
214
393
  }: StateChangesParam<TState, TMetadata, NonNullable<TStateSetter>>) => {
215
- const { asyncStorageKey } = getMetadata();
394
+ const asyncStorageKey = getMetadata()?.asyncStorageKey;
216
395
 
217
396
  if (!asyncStorageKey) return;
218
397
 
@@ -227,566 +406,20 @@ export class GlobalStore<
227
406
  }
228
407
  ```
229
408
 
230
- The methods **formatToStore** and **formatFromStore** are part of another library of my [json-storage-formatter](https://www.npmjs.com/package/json-storage-formatter)...
231
-
232
- 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...
233
-
234
- ## How to use the **GlobalStoreAsync**
235
-
236
- It will work exactly the same as the **GlobalStore**, the main difference is that by default the metadata object will include the {**isAsyncStorageReady** } which will allow you to know if the async data was already retrieved.
237
-
238
- ```ts
239
- const [count, setCount, { isAsyncStorageReady }] = useCountGlobal();
240
- ```
241
-
242
- ...
243
-
244
- 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.
245
-
246
- 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 [GlobalStoreAsync.ts](https://github.com/johnny-quesada-developer/react-native-global-state-hooks/blob/master/%40tests/__test__/GlobalStoreAsyc.ts)
247
-
248
- ...
249
-
250
- ...
251
-
252
- # Restricting the manipulation of the global **state**
253
-
254
- ## Who hate reducers?
255
-
256
- 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**
257
- ...
258
-
259
- ```ts
260
- const initialValue = 0;
261
-
262
- const config = {
263
- // this is not reactive information that you could also store in the async storage
264
- // updating the metadata will not trigger the onStateChanged method or any update on the components
265
- metadata: null,
266
-
267
- // The lifecycle callbacks are: onInit, onStateChanged, onSubscribed and computePreventStateChange
268
- };
269
-
270
- const countStore = new GlobalStore(
271
- initialValue,
272
- config,
273
- {
274
- log: (message: string) => (): void => {
275
- console.log(message);
276
- },
277
-
278
- increase(message: string) {
279
- return (storeTools: StoreTools<number>) => {
280
- this.log(message);
281
-
282
- return storeTools.getState();
283
- };
284
- },
285
-
286
- decrease(message: string) {
287
- return (storeTools: StoreTools<number>) => {
288
- this.log(message);
289
-
290
- return storeTools.getState();
291
- };
292
- },
293
- } as const // the -as const- is necessary to avoid typescript errors
294
- );
295
-
296
- // the way to get the hook is the same as for simple setters
297
- const useCountStore = countStore.getHook();
298
-
299
- // now instead of a setState method, you'll get an actions object
300
- // that contains all the actions that you defined in the setterConfig
301
- const [count, countActions] = useCountStore();
302
-
303
- // count is the current state - 0 (number)
304
- // countActions is an object that contains all the actions that you defined in the setterConfig
305
- // countActions.increase(); // this will increase the count by 1, returns the new count (number)
306
- // countActions.decrease(); // this will decrease the count by 1, returns the new count (number)
307
- ```
308
-
309
- ...
310
-
311
- # Configuration callbacks
312
-
313
- ## config.onInit
314
-
315
- This method will be called once the store is created after the constructor,
316
-
317
- @examples
318
-
319
- ```ts
320
- import { GlobalStore } from 'react-native-global-state-hooks';
321
-
322
- const initialValue = 0;
323
-
324
- const store = new GlobalStore(0, {
325
- onInit: async ({ setMetadata, setState }) => {
326
- const data = await someApiCall();
327
-
328
- setState(data);
329
- setMetadata({ isDataUpdated: true });
330
- },
331
- });
332
- ```
333
-
334
- ...
335
-
336
- ## config.onStateChanged
337
-
338
- This method will be called every time the state is changed
339
-
340
- @examples
341
-
342
- ```ts
343
- import { GlobalStore } from 'react-native-global-state-hooks';
344
-
345
- const store = new GlobalStore(0, {
346
- onStateChanged: ({ getState }) => {
347
- const state = getState();
348
-
349
- console.log(state);
350
- },
351
- });
352
- ```
353
-
354
- ...
355
-
356
- ## config.onSubscribed
357
-
358
- This method will be called every time a component is subscribed to the store
359
-
360
- @examples
361
-
362
- ```ts
363
- import { GlobalStore } from 'react-native-global-state-hooks';
364
-
365
- const store = new GlobalStore(0, {
366
- onSubscribed: ({ getState }) => {
367
- console.log('A component was subscribed to the store');
368
- },
369
- });
370
- ```
371
-
372
- ...
373
-
374
- ## config.computePreventStateChange
375
-
376
- This method will be called every time the state is going to be changed, if it returns true the state won't be changed
377
-
378
- @examples
379
-
380
- ```ts
381
- import { GlobalStore } from 'react-native-global-state-hooks';
382
-
383
- const store = new GlobalStore(0, {
384
- computePreventStateChange: ({ getState }) => {
385
- const state = getState();
386
- const shouldPrevent = state < 0;
387
-
388
- if (shouldPrevent) return true;
389
-
390
- return false;
391
- },
392
- });
393
- ```
394
-
395
- ...
396
-
397
- ...
398
-
399
- ...
400
-
401
- ...
402
-
403
- ...
404
-
405
- # Examples and Comparison:
406
-
407
- ## 1. Lets try to share some state between components
408
-
409
- ### **With the GlobalStore approach, it will look like this:**
410
-
411
- ```tsx
412
- type TUser = {
413
- name: string;
414
- email: string;
415
- };
416
-
417
- const useUserStore = new GlobalStore<TUser>({
418
- name: null,
419
- email: null,
420
- }).getHook();
421
-
422
- const Component = () => {
423
- const [currentUser] = useUserStore();
424
-
425
- return <Text>{currentUser.name}</Text>;
426
- };
427
- ```
428
-
429
- ## Simple, right?
430
-
431
- ### Let's now see how this same thing would look like by using context:
432
-
433
- ```tsx
434
- type TUser = {
435
- name: string;
436
- email: string;
437
- };
438
-
439
- const UserContext = createContext<{
440
- currentUser: TUser;
441
- }>({
442
- currentUser: null,
443
- });
444
-
445
- const UserProvider: React.FC<PropsWithChildren> = ({ children }) => {
446
- const [currentUser, setCurrentUser] = useState<TUser>(null);
447
-
448
- // ...get current user information
449
-
450
- return (
451
- <UserContext.Provider value={{ currentUser }}>
452
- {children}
453
- </UserContext.Provider>
454
- );
455
- };
456
-
457
- const Component = () => {
458
- const { currentUser } = useContext(UserContext);
459
-
460
- return <Text>{currentUser.name}</Text>;
461
- };
462
-
463
- const App = () => {
464
- return (
465
- <UserProvider>
466
- <Component />
467
- </UserProvider>
468
- );
469
- };
470
- ```
471
-
472
- ### We already are able to notice a couple of extra lines right?
473
-
474
- Let's now add another simple store to the equation
475
-
476
- ### **With the GlobalStore approach, it will look like this:**
477
-
478
- ```tsx
479
- type TUser = {
480
- name: string;
481
- email: string;
482
- };
483
-
484
- const useUserStore = new GlobalStore<TUser>({
485
- name: null,
486
- email: null,
487
- }).getHook();
488
-
489
- // we create the store
490
- const useCountStore = new GlobalStore(0).getHook();
491
-
492
- const Component = () => {
493
- const [currentUser] = useUserStore();
494
-
495
- // from the component we consume the new store
496
- const [count, setCount] = useCountStore();
497
-
498
- return <Text>{currentUser.name}</Text>;
499
- };
500
- ```
501
-
502
- With context, we'll have again to create all the boilerplate, and wrap the component into the new provider...
503
-
504
- ### **Lets see that**
505
-
506
- ```tsx
507
- type TUser = {
508
- name: string;
509
- email: string;
510
- };
511
-
512
- const UserContext = createContext<{
513
- currentUser: TUser;
514
- }>({
515
- currentUser: null,
516
- });
517
-
518
- // let's create the context
519
- const CountContext = createContext({
520
- count: 0,
521
- setCount: (() => {
522
- throw new Error('not implemented');
523
- }) as Dispatch<SetStateAction<number>>,
524
- });
525
-
526
- const UserProvider: React.FC<PropsWithChildren> = ({ children }) => {
527
- const [currentUser, setCurrentUser] = useState<TUser>(null);
528
-
529
- // ...
530
-
531
- return (
532
- <UserContext.Provider value={{ currentUser }}>
533
- {children}
534
- </UserContext.Provider>
535
- );
536
- };
537
-
538
- // we also need another provider
539
- const CountProvider: React.FC<PropsWithChildren> = ({ children }) => {
540
- const [count, setCount] = useState(0);
541
-
542
- return (
543
- <CountContext.Provider value={{ count, setCount }}>
544
- {children}
545
- </CountContext.Provider>
546
- );
547
- };
548
-
549
- // we need to wrap the component into the new provider (this is for each future context)
550
- const App = () => {
551
- return (
552
- <UserProvider>
553
- <CountProvider>
554
- <Component />
555
- </CountProvider>
556
- </UserProvider>
557
- );
558
- };
559
-
560
- const Component = () => {
561
- const { currentUser } = useContext(UserContext);
562
-
563
- // finally we are able to get access to the new context...
564
- const { count, setCount } = useContext(CountContext);
565
-
566
- return <Text>{currentUser.name}</Text>;
567
- };
568
- ```
569
-
570
- 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.
571
-
572
- ### 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?
573
-
574
- 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...
575
-
576
- ## Let's see this time first the **context** approach
577
-
578
- ```tsx
579
- type TUser = {
580
- name: string;
581
- email: string;
582
- };
583
-
584
- const UserContext = createContext<{
585
- currentUser: TUser;
586
- }>({
587
- currentUser: null,
588
- });
589
-
590
- // let's remove the setter from this context
591
- const CountContext = createContext({
592
- count: 0,
593
- });
594
-
595
- // lets create another context to share the actions
596
- const CountContextSetter = createContext({
597
- increase: (): void => {
598
- throw new Error('increase is not implemented');
599
- },
600
- decrease: (): void => {
601
- throw new Error('decrease is not implemented');
602
- },
603
- });
604
-
605
- const UserProvider: React.FC<PropsWithChildren> = ({ children }) => {
606
- const [currentUser, setCurrentUser] = useState<TUser>(null);
607
-
608
- // ...
609
-
610
- return (
611
- <UserContext.Provider value={{ currentUser }}>
612
- {children}
613
- </UserContext.Provider>
614
- );
615
- };
616
-
617
- // To don't overcomplicate the example let's just add but providers into this component, that will be enough
618
- const CountProvider: React.FC<PropsWithChildren> = ({ children }) => {
619
- const [count, setCount] = useState(0);
620
-
621
- const increase = () => setCount(count + 1);
622
- const decrease = () => setCount(count - 1);
623
-
624
- return (
625
- //one context is gonna share the edition of the state
626
- <CountContext.Provider value={{ count }}>
627
- {/* this second component will share the mutations of the state */}
628
- <CountContextSetter.Provider value={{ increase, decrease }}>
629
- {children}
630
- </CountContextSetter.Provider>
631
- </CountContext.Provider>
632
- );
633
- };
634
-
635
- // 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**
636
- const App = () => {
637
- return (
638
- <UserProvider>
639
- <CountProvider>
640
- {/* lets create two components instead of one */}
641
- <ComponentSetter />
642
- <Component />
643
- </CountProvider>
644
- </UserProvider>
645
- );
646
- };
647
-
648
- const ComponentSetter = () => {
649
- const { increase, decrease } = useContext(CountContextSetter);
650
-
651
- return (
652
- <View>
653
- <Button title='Increase' onPress={increase} />
654
- <Button title='Decrease' onPress={decrease} />
655
- </View>
656
- );
657
- };
658
-
659
- const Component = () => {
660
- const { currentUser } = useContext(UserContext);
661
-
662
- // finally we are able to get access to the new context...
663
- const { count } = useContext(CountContext);
664
-
665
- return (
666
- <View>
667
- <Text>{currentUser.name}</Text>
668
- <Text>{count}</Text>
669
- </View>
670
- );
671
- };
672
- ```
673
-
674
- Wow, a lot!!! just to be able to separate the mutations... and have mutations!!
675
-
676
- ### it would be easier with the GlobalStore? Let's see.
677
-
678
- ```tsx
679
- type TUser = {
680
- name: string;
681
- email: string;
682
- };
683
-
684
- const useUser = new GlobalStore<TUser>({
685
- name: null,
686
- email: null,
687
- }).getHook();
688
-
689
- // let's modify the store to add custom actions, the second parameter is configuration let's just pass null for now
690
- const countStore = new GlobalStore(0, null, {
691
- increase() {
692
- return ({ setState }: StoreTools<number>) => {
693
- setState((state) => state + 1);
694
- };
695
- },
696
-
697
- decrease() {
698
- return ({ setState }: StoreTools<number>) => {
699
- setState((state) => state - 1);
700
- };
701
- },
702
- } as const);
703
-
704
- const useCount = countStore.getHook();
705
-
706
- // this actions don't use hooks, but are connected to the store and all the subscribers will be notified
707
- const [, countActions] = countStore.getHookDecoupled();
708
-
709
- // this component is not subscribed to the store, so it will not be notified when the state changes
710
- const ComponentSetter = () => {
711
- return (
712
- <View>
713
- <Button title='Increase' onPress={countActions.increase} />
714
- <Button title='Decrease' onPress={countActions.decrease} />
715
- </View>
716
- );
717
- };
718
-
719
- // this component is subscribed to the store, so it will be notified when the state changes
720
- const Component = () => {
721
- const [user] = useUser();
722
- const [count, actions] = useCount();
723
-
724
- return (
725
- <View>
726
- <Text>{count}</Text>
727
- </View>
728
- );
729
- };
730
- ```
731
-
732
- ### So let's analyze what happened
733
-
734
- To restrict the state manipulations with the custom actions, we just need to add a third parameter to the store.
735
-
736
- ```ts
737
- const countStore = new GlobalStore(0, null, {
738
- log: (action: string) => () => console.log(action),
739
-
740
- // every action is a function that returns a function that receives the store tools
741
- increase() {
742
- return ({ setState, getState }: StoreTools<number>): number => {
743
- setState((state) => state + 1);
744
-
745
- // actions are able to communicate between them
746
- this.log('increase');
747
-
748
- return getState();
749
- };
750
- },
751
- } as const);
752
-
753
- // the const is necessary to avoid typescript errors
754
- ```
755
-
756
- All the library is strongly typed, we use generics to return the correct data type in each action.
409
+ Then, from an instance of the global store, you will be able to access the hooks.
757
410
 
758
411
  ```ts
759
- const [, actions] = countStore.getHookDecoupled();
760
-
761
- // for example the type of actions.increase will be: () => number
762
- // just in case, even the parameters of the actions are gonna be exposed through TS
763
- ```
764
-
765
- ## getHookDecoupled
766
-
767
- ### **getHookDecoupled** returns a tuple with the state and the actions,
768
-
769
- This is so useful when you want to use the actions without having to be subscribed to changes of the state.
770
- There is also a third element in the tuple which is a function for getting the metadata of the store
771
-
772
- ### the metadata of the store is not reactive information which could be shared through the store
773
-
774
- ## Adding metadata to the store
775
-
776
- ```tsx
777
- const [, , getMetadata] = new GlobalStore(0, {
412
+ const storage = new GlobalStore(0, {
778
413
  metadata: {
779
- isStoredSynchronized: false,
414
+ asyncStorageKey: 'counter',
415
+ isAsyncStorageReady: false,
780
416
  },
781
- }).getHookDecoupled();
417
+ });
782
418
 
783
- console.log(getMetadata().isStoredSynchronized); // false
419
+ const [getState, _, getMetadata] = storage.getHookDecoupled();
420
+ const useState = storage.getHook();
784
421
  ```
785
422
 
786
- The setMetadata is part of the store tools, so it can be used in the actions, but again the metadata is not reactive!! so it will not trigger a re-render on the subscribers
787
-
788
- ...
789
-
790
- ...
423
+ ### **Note**: The GlobalStore class is still available in the package in case you were already extending from it.
791
424
 
792
425
  # That's it for now!! hope you enjoy coding!!