react-native-global-state-hooks 3.0.7 → 4.0.0
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 +287 -654
- package/lib/GlobalStore.d.ts +90 -24
- package/lib/GlobalStore.types.d.ts +77 -8
- package/lib/GlobalStoreAbstract.d.ts +75 -5
- package/lib/bundle.js +2 -1
- package/lib/bundle.js.LICENSE.txt +1 -0
- package/package.json +1 -1
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
|
|
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
|
|
13
|
+
# Creating a global state
|
|
16
14
|
|
|
17
|
-
We are gonna create a global
|
|
15
|
+
We are gonna create a global state hook **useCount** with one line of code.
|
|
18
16
|
|
|
19
17
|
```ts
|
|
20
|
-
import {
|
|
18
|
+
import { createGlobalState } from 'react-native-global-state-hooks';
|
|
19
|
+
|
|
20
|
+
export const useCount = createGlobalState(0);
|
|
21
|
+
```
|
|
21
22
|
|
|
22
|
-
|
|
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
|
-
|
|
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
|
-
|
|
27
|
+
```ts
|
|
28
|
+
const [count, setCount] = useCount();
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
return <Button onClick={() => setCount((count) => count + 1)}>{count}</Button>;
|
|
31
|
+
```
|
|
32
32
|
|
|
33
|
-
|
|
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
|
-
|
|
37
|
-
export const useCountGlobal = countStore.getHook();
|
|
35
|
+
# Decoupled state access
|
|
38
36
|
|
|
39
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
60
|
-
import { useCountGlobal } from './useCountGlobal'
|
|
58
|
+
# State actions
|
|
61
59
|
|
|
62
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
73
|
-
|
|
66
|
+
```ts
|
|
67
|
+
import { createGlobalState } from 'react-native-global-state-hooks';
|
|
74
68
|
|
|
75
|
-
|
|
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
|
+
} as const,
|
|
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.
|
|
81
|
+
|
|
82
|
+
Let's see how that will look into a react component:
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
const [count, actions] = useCount();
|
|
77
86
|
|
|
78
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
} as const,
|
|
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:
|
|
90
128
|
|
|
91
|
-
|
|
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
|
+
} as const,
|
|
149
|
+
});
|
|
150
|
+
```
|
|
92
151
|
|
|
93
|
-
|
|
152
|
+
In the example the hook will work the same and you'll have access to the correct typing.
|
|
94
153
|
|
|
95
|
-
|
|
154
|
+
# Extending Global Hooks
|
|
155
|
+
|
|
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 {
|
|
161
|
+
import { formatFromStore, formatToStore, createCustomGlobalState } = 'react-native-global-state-hooks'
|
|
99
162
|
|
|
100
|
-
|
|
163
|
+
// Optional configuration available for the consumers of the builder
|
|
164
|
+
type HookConfig = {
|
|
165
|
+
asyncStorageKey?: string;
|
|
166
|
+
};
|
|
101
167
|
|
|
102
|
-
//
|
|
103
|
-
|
|
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
|
-
|
|
106
|
-
|
|
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
|
-
|
|
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
|
-
|
|
114
|
-
|
|
197
|
+
if (storedItem === null) {
|
|
198
|
+
return setState((state) => state, { forceUpdate: true });
|
|
199
|
+
}
|
|
115
200
|
|
|
116
|
-
const
|
|
117
|
-
|
|
201
|
+
const parsed = formatFromStore(storedItem, {
|
|
202
|
+
jsonParse: true,
|
|
203
|
+
});
|
|
118
204
|
|
|
119
|
-
|
|
120
|
-
}
|
|
205
|
+
setState(parsed, { forceUpdate: true });
|
|
206
|
+
},
|
|
121
207
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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.
|
|
137
225
|
|
|
138
|
-
|
|
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).
|
|
139
227
|
|
|
140
|
-
|
|
228
|
+
Let's see how to create a global state using our new builder:
|
|
141
229
|
|
|
142
|
-
|
|
230
|
+
```ts
|
|
231
|
+
const useTodos = createGlobalState(new Map<string, number>(), {
|
|
232
|
+
config: {
|
|
233
|
+
asyncStorageKey: 'todos',
|
|
234
|
+
},
|
|
235
|
+
});
|
|
236
|
+
```
|
|
143
237
|
|
|
144
|
-
|
|
238
|
+
That's correct! If you add an **asyncStorageKey** to the state configuration, the state will be synchronized with the **asyncStorage**
|
|
145
239
|
|
|
146
|
-
|
|
240
|
+
Let's see how to use this async storage hook into our components:
|
|
147
241
|
|
|
148
242
|
```ts
|
|
149
|
-
|
|
150
|
-
|
|
243
|
+
const [todos, setTodos, metadata] = useTodos();
|
|
244
|
+
|
|
245
|
+
return (<>
|
|
246
|
+
{metadata.isAsyncStorageReady ? <TodoList todos={todos} /> : <Text>Loading...</Text>}
|
|
247
|
+
<>);
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
The **metadata** is not reactive information and can only be modified from inside the global state lifecycle methods.
|
|
151
251
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
252
|
+
# Life cycle methods
|
|
253
|
+
|
|
254
|
+
There are some lifecycle methods available for use with global hooks, let's review them:
|
|
255
|
+
|
|
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
|
+
```
|
|
308
|
+
|
|
309
|
+
You can pass this callbacks between on the second parameter of the builders like **createGlobalState**
|
|
310
|
+
|
|
311
|
+
```ts
|
|
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);
|
|
159
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.
|
|
330
|
+
|
|
331
|
+
Let's see an example again with the **asyncStorage** custom global hook but with the abstract class.
|
|
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
|
-
|
|
347
|
+
actionsConfig: TStateSetter | null = null
|
|
174
348
|
) {
|
|
175
|
-
super(state, config,
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
409
|
+
Then, from an instance of the global store, you will be able to access the hooks.
|
|
361
410
|
|
|
362
411
|
```ts
|
|
363
|
-
|
|
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.
|
|
757
|
-
|
|
758
|
-
```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
|
-
|
|
414
|
+
asyncStorageKey: 'counter',
|
|
415
|
+
isAsyncStorageReady: false,
|
|
780
416
|
},
|
|
781
|
-
})
|
|
417
|
+
});
|
|
782
418
|
|
|
783
|
-
|
|
419
|
+
const [getState, _, getMetadata] = storage.getHookDecoupled();
|
|
420
|
+
const useState = storage.getHook();
|
|
784
421
|
```
|
|
785
422
|
|
|
786
|
-
|
|
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!!
|