react-global-state-hooks 1.1.2 → 2.0.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,694 +1,769 @@
1
1
  # react-global-state-hooks
2
2
 
3
- This is a package to easily handling global-state across your react 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 an introduction video [here!](https://www.youtube.com/watch?v=WfoMhO1zZ04)
3
+ This is a package to easily handling **global state hooks** across your **react components**
6
4
 
7
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/)
8
6
 
9
- ...
10
-
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).
12
8
 
13
- # Creating a global store
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**
14
10
 
15
- We are gonna create a global count example **useCountGlobal.ts**:
16
-
17
- ```ts
18
- import { GlobalStore } from 'react-global-state-hooks';
11
+ You can also see an introduction video [here!](https://www.youtube.com/watch?v=WfoMhO1zZ04&t=8s)
19
12
 
20
- // initialize your store with the default value of the same.
21
- const countStore = new GlobalStore(0);
13
+ # Creating a global state
22
14
 
23
- // get the hook
24
- export const useCountGlobal = countStore.getHook();
15
+ We are gonna create a global state hook **useCount** with one line of code.
25
16
 
26
- // inside your component just call...
27
- const [count, setCount] = useCountGlobal(); // no paremeters are needed since this is a global store
28
-
29
- // That's it, that's a global store... Strongly typed, with a global-hook that we could reuse cross all our react-components.
17
+ ```ts
18
+ import { createGlobalState } from 'react-global-state-hooks';
30
19
 
31
- // #### Optionally you are able to use a decoupled hook,
32
- // #### This function is linked to the store hooks but is not a hook himself.
20
+ export const useCount = createGlobalState(0);
21
+ ```
33
22
 
34
- export const [getCount, sendCount] = countStore.getHookDecoupled();
23
+ That's it! Welcome to global hooks. Now, you can use this state wherever you need it in your application.
35
24
 
36
- // @example
37
- console.log(getCount()); // 0;
25
+ Let's see how to use it inside a simple **component**
38
26
 
39
- // components subscribed to the global hook if there are so
40
- sendCount(5);
27
+ ```ts
28
+ const [count, setCount] = useCount();
41
29
 
42
- console.log(getCount()); // 5;
30
+ return <button onclick={() => setCount((count) => count + 1)}>{count}</button>;
43
31
  ```
44
32
 
45
- ...
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.
34
+
35
+ # Selectors
46
36
 
47
- ...
37
+ What if you already have a global state that you want to subscribe to, but you don't want your component to listen to all the changes of the state, only a small portion of it? Let's create a more complex **state**
48
38
 
49
- # Implementing your global hook into your components
39
+ ```ts
40
+ import { createGlobalState } from 'react-global-state-hooks';
50
41
 
51
- Let's say we have two components **MyFirstComponent**, **MySecondComponent**, in order to use our global hook they will look just like:
42
+ export const useContacts = createGlobalState({
43
+ isLoading: true,
44
+ filter: '',
45
+ items: [] as Contact[],
46
+ });
47
+ ```
52
48
 
53
- ```JSX
54
- import { useCountGlobal } from './useCountGlobal'
49
+ Now, let's say we want to have a filter bar for the contacts that will only have access to the filter.
55
50
 
56
- const MyFirstComponent: React.FC = () => {
57
- const [count, setter] = useCountGlobal();
58
- const onClickAddOne = () => setter(count + 1);
51
+ **FilterBar.tsx**
59
52
 
60
- return (<button onClick={onClickAddOne}>{`count: ${count}`}</button>);
61
- }
53
+ ```ts
54
+ const [{ filter }, setState] = useContacts(({ filter }) => ({ filter }));
55
+
56
+ return (
57
+ <input
58
+ onChange={(event) =>
59
+ setState((state) => ({ ...state, filter: event.target.value }))
60
+ }
61
+ />
62
+ );
63
+ ```
62
64
 
63
- const MySecondComponent: React.FC = () => {
64
- const [count, setter] = useCountGlobal();
65
+ There you have it again, super simple! By adding a **selector** function, you are able to create a derivative hook that will only trigger when the result of the **selector** changes.
65
66
 
66
- // it can also be use as a normal setter into a callback or other hooks
67
- const onClickAddTwo = useCallback(() => setter(state => state + 2), [])
67
+ By the way, in the example, the **selector** returning a new object is not a problem at all. This is because, by default, there is a shallow comparison between the previous and current versions of the state, so the render won't trigger if it's not necessary.
68
68
 
69
- return (<button onClick={onClickAddOne}>{`count: ${count}`}</button>);
70
- }
69
+ ## What if you want to reuse the selector?
71
70
 
72
- // It's so simple to share information between components
73
- ```
71
+ It will be super common to have the necessity of reusing a specific **selector**, and it can be a little annoying to have to do the same thing again and again. Right?
74
72
 
75
- 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:
73
+ No problem, you can create a reusable **derivative-state** and use it across your components. Let's create one for our filter.
76
74
 
77
75
  ```ts
78
- const countStore = new GlobalStore(0);
76
+ const useFilter = createDerivate(useContacts, ({ filter }) => ({ filter }));
79
77
  ```
80
78
 
81
- ...
82
-
83
- ...
84
-
85
- # Persisting state into localhost
79
+ Well, that's it! Now you can simply call **useFilter** inside your component, and everything will continue to work the same.
86
80
 
87
- if you want to persist the value of the store into the. **localstorage**, you only have to add the property **localStorageKey** into the configuration parameter.
81
+ **FilterBar.tsx**
88
82
 
89
83
  ```ts
90
- const countStore = new GlobalStore(0, {
91
- localStorageKey: 'my_persisted_state',
92
-
93
- // by default, the state is encrypted to base64, but you can disable it, or use a custom encryptor
94
- encrypt: false,
95
-
96
- // by default, the state is encrypted to base64, but you can disable it, or use a custom decrypt
97
- decrypt: false,
98
- });
84
+ const [{ filter }, setState] = useFilter();
85
+
86
+ return (
87
+ <input
88
+ onChange={(event) =>
89
+ setState((state) => ({ ...state, filter: event.target.value }))
90
+ }
91
+ />
92
+ );
99
93
  ```
100
94
 
101
- ...
102
-
103
- ...
95
+ Notice that the **state** changes, but the **setter** does not. This is because this is a **DERIVATE state**, and it cannot be directly changed. It will always be derived from the main hook.
104
96
 
105
- # Decoupled hook
97
+ # State actions
106
98
 
107
- If you want to access the global state outside a component or outside a hook, or without subscribing the component to the state changes...
99
+ 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 **useContacts**.
108
100
 
109
- 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.
101
+ By defining a custom API for the **useContacts**, 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 stick to the desired restrictions.
110
102
 
111
103
  ```ts
112
- import { GlobalStore } from 'react-global-state-hooks';
113
-
114
- const countStore = new GlobalStore(0);
104
+ import { createGlobalState } from 'react-global-state-hooks';
115
105
 
116
- // remember this should be used as the **useState** hook.
117
- export const useCountGlobal = countStore.getHook();
106
+ const initialState = {
107
+ isLoading: true,
108
+ filter: '',
109
+ items: [] as Contact[],
110
+ };
118
111
 
119
- // this functions are not hooks, and they can be used in whatever place into your code, ClassComponents, OtherHooks, Services etc.
120
- export const [getCount, sendCount] = countStore.getHookDecoupled();
112
+ type State = typeof initialState;
113
+
114
+ export const useContacts = createGlobalState(initialState, {
115
+ // this are the actions available for this state
116
+ actions: {
117
+ setFilter(filter: string) {
118
+ return ({ setState }: StoreTools<State>) => {
119
+ setState((state) => ({
120
+ ...state,
121
+ filter,
122
+ }));
123
+ };
124
+ },
125
+ } as const,
126
+ onInit: async ({ setState }: StoreTools<State>) => {
127
+ // fetch contacts
128
+ },
129
+ });
121
130
  ```
122
131
 
123
- Let's see a trivial example:
124
-
125
- ...
126
-
127
- ```JSX
128
- import { useCountGlobal, sendCount } from './useCountGlobal'
132
+ That's it! In this updated version, the **useContacts** 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.
129
133
 
130
- const CountDisplayerComponent: React.FC = () => {
131
- const [count] = useCountGlobal();
134
+ Let's see how that will look now into our **FilterBar.tsx**
132
135
 
133
- return (<label>{count}<label/>);
134
- }
136
+ ```tsx
137
+ const [{ filter }, { setFilter }] = useFilter();
135
138
 
136
- // here we have a separate component that is gonna handle the state of the previous component we created,
137
- // this new component is not gonna be affected by the changes applied on <CountDisplayerComponent/>
138
- // Stage2 does not need to be updated once the global count changes
139
- const CountManagerComponent: React.FC = () => {
140
- const increaseClick = useCallback(() => sendCount(count => count + 1), []);
141
- const decreaseClick = useCallback(() => sendCount(count => count - 1), []);
142
-
143
- return (<>
144
- <button onClick={increaseClick} >increase</button>
145
- <button onClick={decreaseClick} >decrease</button>
146
- </>);
147
- }
139
+ return <input onChange={(event) => setFilter(event.target.value)} />;
148
140
  ```
149
141
 
150
- ...
151
-
152
- ...
153
-
154
- # Restricting the manipulation of the global **state**
155
-
156
- ## Who hate reducers?
142
+ Yeah, that's it! All the **derived states** and **emitters** (we will talk about this later) will inherit the new actions interface.
157
143
 
158
- 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**
159
- ...
144
+ You can even **derive** from another **derived state**! Let's explore a few silly examples:
160
145
 
161
146
  ```ts
162
- const initialValue = 0;
147
+ const useFilter = createDerivate(useContacts, ({ filter }) => ({ filter }));
163
148
 
164
- const config = {
165
- // this is not reactive information that you could also store in the async storage
166
- // upating the metadata will not trigger the onStateChanged method or any update on the components
167
- metadata: null,
149
+ const useFilterString = createDerivate(useFilter, { filter } => filter);
168
150
 
169
- // The lifecycle callbacks are: onInit, onStateChanged, onSubscribed and computePreventStateChange
170
- };
151
+ const useContacts = createDerivate(useContacts, ({ items }) => items);
171
152
 
172
- const countStore = new GlobalStore(
173
- initialValue,
174
- config,
175
- {
176
- log: (message: string) => (): void => {
177
- console.log(message);
178
- },
153
+ const useContactsLength = createDerivate(useContacts, (items) => items.length);
179
154
 
180
- increase(message: string) {
181
- return (storeTools: StoreTools<number>) => {
182
- this.log(message);
155
+ const useIsContactsEmpty = createDerivate(useContactsLength, (length) => !length);
156
+ ```
183
157
 
184
- return storeTools.getState();
185
- };
186
- },
158
+ It can't get any simpler, right? Everything is connected, everything is reactive. Plus, these hooks are strongly typed, so if you're working with **TypeScript**, you'll absolutely love it.
187
159
 
188
- decrease(message: string) {
189
- return (storeTools: StoreTools<number>) => {
190
- this.log(message);
160
+ # Decoupled state access
191
161
 
192
- return storeTools.getState();
193
- };
194
- },
195
- } as const // the -as const- is necessary to avoid typescript errors
196
- );
162
+ If you need to access the global state outside of a component or a hook without subscribing to state changes, or even inside a **ClassComponent**, you can use the **createGlobalStateWithDecoupledFuncs**.
197
163
 
198
- // the way to get the hook is the same as for simple setters
199
- const useCountStore = countStore.getHook();
164
+ 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.
200
165
 
201
- // now instead of a setState method, you'll get an actions object
202
- // that contains all the actions that you defined in the setterConfig
203
- const [count, countActions] = useCountStore();
166
+ 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:
204
167
 
205
- // count is the current state - 0 (number)
206
- // countActions is an object that contains all the actions that you defined in the setterConfig
207
- // countActions.increase(); // this will increase the count by 1, returns the new count (number)
208
- // countActions.decrease(); // this will decrease the count by 1, returns the new count (number)
168
+ ```ts
169
+ import { createGlobalStateWithDecoupledFuncs } from 'react-global-state-hooks';
170
+
171
+ export const [useContacts, contactsGetter, contactsSetter] =
172
+ createGlobalStateWithDecoupledFuncs({
173
+ isLoading: true,
174
+ filter: '',
175
+ items: [] as Contact[],
176
+ });
209
177
  ```
210
178
 
211
- ...
212
-
213
- # Configuration callbacks
214
-
215
- ## config.onInit
216
-
217
- This method will be called once the store is created after the constructor,
179
+ That's great! With the addition of the **contactsGetter** and **contactsSetter** methods, you now have the ability to access and modify the state without the need for subscription to the hook.
218
180
 
219
- @examples
181
+ While **useContacts** will allow your components to subscribe to the custom hook, using the **contactsGetter** method you will be able retrieve the current value of the state. This allows you to access the state whenever necessary, without being reactive to its changes. Let' see how:
220
182
 
221
183
  ```ts
222
- import { GlobalStore } from 'react-global-state-hooks';
184
+ // To synchronously get the value of the state
185
+ const value = contactsGetter();
223
186
 
224
- const initialValue = 0;
187
+ // the type of value will be { isLoading: boolean; filter: string; items: Contact[] }
188
+ ```
225
189
 
226
- const store = new GlobalStore(0, {
227
- onInit: async ({ setMetadata, setState }) => {
228
- const data = await someApiCall();
190
+ Additionally, to subscribe to state changes, you can pass a callback function as a parameter to the **getter**. This approach enables you to create a subscription group, allowing you to subscribe to either the entire state or a specific portion of it. When a callback function is provided to the **getter**, it will return a cleanup function instead of the state. This cleanup function can be used to unsubscribe or clean up the subscription when it is no longer needed.
229
191
 
230
- setState(data);
231
- setMetadata({ isDataUpdated: true });
232
- },
192
+ ```ts
193
+ /**
194
+ * This not only allows you to retrieve the current value of the state...
195
+ * but also enables you to subscribe to any changes in the state or a portion of it
196
+ */
197
+ const removeSubscriptionGroup = contactsGetter<Subscribe>((subscribe) => {
198
+ subscribe((state) => {
199
+ console.log('state changed: ', state);
200
+ });
201
+
202
+ subscribe(
203
+ (state) => state.isLoading,
204
+ (isLoading) => {
205
+ console.log('is loading changed', isLoading);
206
+ }
207
+ );
233
208
  });
234
209
  ```
235
210
 
236
- ...
211
+ That's great, isn't it? everything stays synchronized with the original state!!
237
212
 
238
- ## config.onStateChanged
213
+ # Emitters
239
214
 
240
- This method will be called every time the state is changed
241
-
242
- @examples
215
+ So, we have seen that we can subscribe a callback to state changes, create **derivative states** from our global hooks, **and derive hooks from those derivative states**. Guess what? We can also create derivative **emitters** and subscribe callbacks to specific portions of the state. Let's review it:
243
216
 
244
217
  ```ts
245
- import { GlobalStore } from 'react-global-state-hooks';
246
-
247
- const store = new GlobalStore(0, {
248
- onStateChanged: ({ getState }) => {
249
- const state = getState();
250
-
251
- console.log(state);
252
- },
253
- });
218
+ const subscribeToFilter = createDerivateEmitter(
219
+ contactsGetter,
220
+ ({ filter }) => ({
221
+ filter,
222
+ })
223
+ );
254
224
  ```
255
225
 
256
- ...
257
-
258
- ## config.onSubscribed
259
-
260
- This method will be called every time a component is subscribed to the store
226
+ Cool, it's basically the same, but instead of using the **hook** as a parameter, we just have to use the **getter** as a parameter, and that will make the magic.
261
227
 
262
- @examples
228
+ Now we are able to add a callback that will be executed every time the state of the **filter** changes.
263
229
 
264
230
  ```ts
265
- import { GlobalStore } from 'react-global-state-hooks';
266
-
267
- const store = new GlobalStore(0, {
268
- onSubscribed: ({ getState }) => {
269
- console.log('A component was subscribed to the store');
270
- },
231
+ const removeFilterSubscription = subscribeToFilter<Subscribe>(({ filter }) => {
232
+ console.log(`The filter value changed: ${filter}`);
271
233
  });
272
234
  ```
273
235
 
274
- ...
275
-
276
- ## config.computePreventStateChange
277
-
278
- This method will be called every time the state is going to be changed, if it returns true the state won't be changed
279
-
280
- @examples
236
+ By default, the callback will be executed once subscribed, using the current value of the state. If you want to avoid this initial call, you can pass an extra parameter to the **subscribe** function.
281
237
 
282
238
  ```ts
283
- import { GlobalStore } from 'react-global-state-hooks';
284
-
285
- const store = new GlobalStore(0, {
286
- computePreventStateChange: ({ getState }) => {
287
- const state = getState();
288
- const shouldPrevent = state < 0;
289
-
290
- if (shouldPrevent) return true;
291
-
292
- return false;
239
+ const removeFilterSubscription = subscribeToFilter<Subscribe>(
240
+ ({ filter }) => {
241
+ console.log(`The filter value changed: ${filter}`);
293
242
  },
294
- });
243
+ {
244
+ skipFirst: true,
245
+ }
246
+ );
295
247
  ```
296
248
 
297
- ...
249
+ Also, of course, if you have an exceptional case where you want to derivate directly from the current **emitter**, you can add a **selector**. This allows you to fine-tune the emitted values based on your requirements
298
250
 
299
- ...
251
+ ```ts
252
+ const removeFilterSubscription = subscribeToFilter<Subscribe>(
253
+ ({ filter }) => filter,
254
+ /**
255
+ * Cause of the selector the filter now is an string
256
+ */
257
+ (filter) => {
258
+ console.log(`The filter value changed: ${filter}`);
259
+ },
260
+ {
261
+ skipFirst: true,
262
+ /**
263
+ * You can also override the default shallow comparison...
264
+ * or disable it completely by setting the isEqual callback to null.
265
+ */
266
+ isEqual: (a, b) => a === b,
267
+ // isEqual: null // this will avoid doing a shallow comparison
268
+ }
269
+ );
270
+ ```
300
271
 
301
- ...
272
+ And guess what again? You can also derive emitters from derived emitters without any trouble at all! It works basically the same. Let's see an example:
302
273
 
303
- ...
274
+ ```ts
275
+ const subscribeToItems = createDerivateEmitter(
276
+ contactsGetter,
277
+ ({ items }) => items
278
+ );
304
279
 
305
- ...
280
+ const subscribeToItemsLength = createDerivateEmitter(
281
+ subscribeToItems,
282
+ (items) => items.length
283
+ );
284
+ ```
306
285
 
307
- # Examples and Comparison:
286
+ The examples may seem a little silly, but they allow you to see the incredible things you can accomplish with these **derivative states** and **emitters**. They open up a world of possibilities!
308
287
 
309
- ## 1. Lets try to share some state between components
288
+ # Combining getters
310
289
 
311
- ### **With the GlobalStore approach, it will look like this:**
290
+ What if you have two states and you want to combine them? You may have already guessed it right? ... you can create combined **emitters** and **hooks** from the hook **getters**.
312
291
 
313
- ```tsx
314
- type TUser = {
315
- name: string;
316
- email: string;
317
- };
292
+ By utilizing the approach of combining **emitters** and **hooks**, you can effectively merge multiple states and make them shareable. This allows for better organization and simplifies the management of the combined states. You don't need to refactor everything; you just need to combine the **global state hooks** you already have. Let's see a simple example:
318
293
 
319
- const useUserStore = new GlobalStore<TUser>({
320
- name: null,
321
- email: null,
322
- }).getHook();
294
+ Fist we are gonna create a couple of **global state**, is important to create them with the **createGlobalStateWithDecoupledFuncs** since we need the decoupled **getter**. (In case you are using an instance of **GlobalStore** or **GlobalStoreAbstract** you can just pick up the getters from the **getHookDecoupled** method)
323
295
 
324
- const Component = () => {
325
- const [currentUser] = useUserStore();
296
+ ```ts
297
+ const [useHook1, getter1, setter1] = createGlobalStateWithDecoupledFuncs({
298
+ propA: 1,
299
+ propB: 2,
300
+ });
326
301
 
327
- return <Text>{currentUser.name}</Text>;
328
- };
302
+ const [, getter2] = createGlobalStateWithDecoupledFuncs({
303
+ propC: 3,
304
+ propD: 4,
305
+ });
329
306
  ```
330
307
 
331
- ## Simple, right?
308
+ Okay, cool, the first state as **propA, propB** while the second one has **propC, propD**, let's combine them:
332
309
 
333
- ### Let's now see how this same thing would look like by using context:
310
+ ```ts
311
+ const [useCombinedHook, getter, dispose] = combineAsyncGetters(
312
+ {
313
+ selector: ([state1, state2]) => ({
314
+ ...state1,
315
+ ...state2,
316
+ }),
317
+ },
318
+ getter1,
319
+ getter2
320
+ );
321
+ ```
334
322
 
335
- ```tsx
336
- type TUser = {
337
- name: string;
338
- email: string;
339
- };
323
+ Well, that's it! Now you have access to a **getter** that will return the combined value of the two states. From this new **getter**, you can retrieve the value or subscribe to its changes. Let'see:
340
324
 
341
- const UserContext = createContext<{
342
- currentUser: TUser;
343
- }>({
344
- currentUser: null,
325
+ ```ts
326
+ const value = getter(); // { propA, propB, propC, propD }
327
+
328
+ // subscribe to the new emitter
329
+ const unsubscribeGroup = getter<Subscribe>((subscribe) => {
330
+ subscribe((state) => {
331
+ console.log(subscribe); // full state
332
+ });
333
+
334
+ // Please note that if you add a selector,
335
+ // the callback will only trigger if the result of the selector changes.
336
+ subscribe(
337
+ ({ propA, propD }) => ({ propA, propD }),
338
+ (derivate) => {
339
+ console.log(derivate); // { propA, propD }
340
+ }
341
+ );
345
342
  });
343
+ ```
346
344
 
347
- const UserProvider: React.FC<PropsWithChildren> = ({ children }) => {
348
- const [currentUser, setCurrentUser] = useState<TUser>(null);
345
+ Regarding the newly created hook, **useCombinedHook**, you can seamlessly utilize it across all your components, just like your other **global state hooks**. This enables a consistent and familiar approach for accessing and managing the combined state within your application.
349
346
 
350
- // ...get current user information
347
+ ```ts
348
+ const [combinedState] = useCombinedHook();
349
+ ```
351
350
 
352
- return (
353
- <UserContext.Provider value={{ currentUser }}>
354
- {children}
355
- </UserContext.Provider>
356
- );
357
- };
351
+ The main difference with **combined hooks** compared to individual **global state hooks** is the absence of **metadata** and **actions**. Instead, combined hooks provide a condensed representation of the underlying global states using simple React functionality. This streamlined approach ensures lightweight usage, making it easy to access and manage the combined state within your components.
358
352
 
359
- const Component = () => {
360
- const { currentUser } = useContext(UserContext);
353
+ ### Let's explore some additional examples.
361
354
 
362
- return <Text>{currentUser.name}</Text>;
363
- };
355
+ Similar to your other **global state hooks**, **combined hooks** allow you to use **selectors** directly from consumer components. This capability eliminates the need to create an excessive number of reusable hooks if they are not truly necessary. By utilizing selectors, you can efficiently extract specific data from the **combined state** and utilize it within your components. This approach offers a more concise and focused way of accessing the required state values without the need for creating additional hooks unnecessarily.
364
356
 
365
- const App = () => {
366
- return (
367
- <UserProvider>
368
- <Component />
369
- </UserProvider>
370
- );
371
- };
357
+ ```ts
358
+ const [fragment] = useCombinedHook(({ propA, propD }) => ({ propA, propD }));
372
359
  ```
373
360
 
374
- ### We already are able to notice a couple of extra lines right?
361
+ Lastly, you have the flexibility to continue combining getters if desired. This means you can extend the functionality of combined hooks by adding more getters to merge additional states. By combining getters in this way, you can create a comprehensive and unified representation of the combined states within your application. This approach allows for modular and scalable state management, enabling you to efficiently handle complex state compositions.
375
362
 
376
- Let's now add another simple store to the equation
363
+ Let's see an example:
377
364
 
378
- ### **With the GlobalStore approach, it will look like this:**
379
-
380
- ```tsx
381
- type TUser = {
382
- name: string;
383
- email: string;
384
- };
385
-
386
- const useUserStore = new GlobalStore<TUser>({
387
- name: null,
388
- email: null,
389
- }).getHook();
365
+ ```ts
366
+ const [useCombinedHook, combinedGetter1, dispose1] = combineAsyncGetters(
367
+ {
368
+ selector: ([state1, state2]) => ({
369
+ ...state1,
370
+ ...state2,
371
+ }),
372
+ },
373
+ getter1,
374
+ getter2
375
+ );
390
376
 
391
- // we create the store
392
- const useCountStore = new GlobalStore(0).getHook();
377
+ const [useHook3, getter3, setter3] = createGlobalStateWithDecoupledFuncs({
378
+ propE: 1,
379
+ propF: 2,
380
+ });
393
381
 
394
- const Component = () => {
395
- const [currentUser] = useUserStore();
382
+ const [useIsLoading, isLoadingGetter, isLoadingSetter] =
383
+ createGlobalStateWithDecoupledFuncs(false);
384
+ ```
396
385
 
397
- // from the component we consume the new store
398
- const [count, setCount] = useCountStore();
386
+ Once we created another peace of state, we can combine it with our other **global hooks** and **emitters**
399
387
 
400
- return <label>{currentUser.name}</label>;
401
- };
388
+ ```ts
389
+ const [useCombinedHook2, combinedGetter2, dispose2] = combineAsyncGetters(
390
+ {
391
+ selector: ([state1, state2, isLoading]) => ({
392
+ ...state1,
393
+ ...state2,
394
+ isLoading,
395
+ }),
396
+ },
397
+ combinedGetter1,
398
+ getter3,
399
+ isLoadingGetter
400
+ );
402
401
  ```
403
402
 
404
- With context, we'll have again to create all the boilerplate, and wrap the component into the new provider...
403
+ You have the freedom to combine as many global hooks as you wish. This means you can merge multiple states into a single cohesive unit by combining their respective hooks. This approach offers flexibility and scalability, allowing you to handle complex state compositions in a modular and efficient manner.
405
404
 
406
- ### **Lets see that**
405
+ ### **Quick note**:
407
406
 
408
- ```tsx
409
- type TUser = {
410
- name: string;
411
- email: string;
412
- };
407
+ Please be aware that the third parameter is a **dispose callback**, which can be particularly useful in **high-order** functions when you want to release any resources associated with the hook. By invoking the dispose callback, the hook will no longer report any changes, ensuring that resources are properly cleaned up. This allows for efficient resource management and can be beneficial in scenarios where you need to handle resource cleanup or termination in a controlled manner.
413
408
 
414
- const UserContext = createContext<{
415
- currentUser: TUser;
416
- }>({
417
- currentUser: null,
418
- });
409
+ ## Setter
419
410
 
420
- // let's create the context
421
- const CountContext = createContext({
422
- count: 0,
423
- setCount: (() => {
424
- throw new Error('not implemented');
425
- }) as Dispatch<SetStateAction<number>>,
426
- });
411
+ Similarly, the **contactsSetter** method allows you to modify the state stored in **useContacts**. You can use this method to update the state with a new value or perform any necessary state mutations without the restrictions imposed by **hooks**.
427
412
 
428
- const UserProvider: React.FC<PropsWithChildren> = ({ children }) => {
429
- const [currentUser, setCurrentUser] = useState<TUser>(null);
413
+ These additional methods provide a more flexible and granular way to interact with the state managed by **useContacts**. You can retrieve and modify the state as needed, without establishing a subscription relationship or reactivity with the state changes.
430
414
 
431
- // ...
415
+ Let's add more actions to the state and explore how to use one action from inside another.
432
416
 
433
- return (
434
- <UserContext.Provider value={{ currentUser }}>
435
- {children}
436
- </UserContext.Provider>
437
- );
438
- };
417
+ Here's an example of adding multiple actions to the state and utilizing one action within another:
439
418
 
440
- // we also need another provider
441
- const CountProvider: React.FC<PropsWithChildren> = ({ children }) => {
442
- const [count, setCount] = useState(0);
419
+ ```ts
420
+ import { createGlobalState } from 'react-global-state-hooks';
443
421
 
444
- return (
445
- <CountContext.Provider value={{ count, setCount }}>
446
- {children}
447
- </CountContext.Provider>
448
- );
449
- };
422
+ export const useCount = createGlobalState(0, {
423
+ actions: {
424
+ log: (currentValue: string) => {
425
+ return ({ getState }: StoreTools<number>): void => {
426
+ console.log(`Current Value: ${getState()}`);
427
+ };
428
+ },
450
429
 
451
- // we need to wrap the component into the new provider (this is for each future context)
452
- const App = () => {
453
- return (
454
- <UserProvider>
455
- <CountProvider>
456
- <Component />
457
- </CountProvider>
458
- </UserProvider>
459
- );
460
- };
430
+ increase(value: number = 1) {
431
+ return ({ getState, setState, actions }: StoreTools<number>) => {
432
+ setState((count) => count + value);
461
433
 
462
- const Component = () => {
463
- const { currentUser } = useContext(UserContext);
434
+ actions.log(message);
435
+ };
436
+ },
464
437
 
465
- // finally we are able to get access to the new context...
466
- const { count, setCount } = useContext(CountContext);
438
+ decrease(value: number = 1) {
439
+ return ({ getState, setState, actions }: StoreTools<number>) => {
440
+ setState((count) => count - value);
467
441
 
468
- return <label>{currentUser.name}</label>;
469
- };
442
+ actions.log(message);
443
+ };
444
+ },
445
+ } as const,
446
+ });
470
447
  ```
471
448
 
472
- 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.
473
-
474
- ### 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?
449
+ 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.
475
450
 
476
- 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...
451
+ 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:
477
452
 
478
- ## Let's see this time first the **context** approach
453
+ ```ts
454
+ import { createGlobalStateWithDecoupledFuncs } from 'react-global-state-hooks';
455
+
456
+ export const [useCount, getCount, $actions] =
457
+ createGlobalStateWithDecoupledFuncs(0, {
458
+ actions: {
459
+ log: (currentValue: string) => {
460
+ return ({ getState }: StoreTools<number>): void => {
461
+ console.log(`Current Value: ${getState()}`);
462
+ };
463
+ },
464
+
465
+ increase(value: number = 1) {
466
+ return ({ getState, setState }: StoreTools<number>) => {
467
+ setState((count) => count + value);
468
+
469
+ $actions.log(message);
470
+ };
471
+ },
472
+ } as const,
473
+ });
474
+ ```
479
475
 
480
- ```tsx
481
- type TUser = {
482
- name: string;
483
- email: string;
484
- };
476
+ In the example the hook will work the same and you'll have access to the correct typing.
485
477
 
486
- const UserContext = createContext<{
487
- currentUser: TUser;
488
- }>({
489
- currentUser: null,
490
- });
478
+ # Local Storage
491
479
 
492
- // let's remove the setter from this context
493
- const CountContext = createContext({
494
- count: 0,
495
- });
480
+ By default, our global hooks are capable of persisting information in local storage. To achieve this, you need to provide the key that will be used to persist the data. Additionally, you have the option to encrypt the stored data.
496
481
 
497
- // lets create another context to share the actions
498
- const CountContextSetter = createContext({
499
- increase: (): void => {
500
- throw new Error('increase is not implemented');
501
- },
502
- decrease: (): void => {
503
- throw new Error('decrease is not implemented');
482
+ ```ts
483
+ const useContacts = createGlobalState(
484
+ {
485
+ filter: '',
486
+ items: [] as Contact[],
504
487
  },
505
- });
488
+ {
489
+ localStorage: {
490
+ key: 'data',
491
+ },
492
+ }
493
+ );
494
+ ```
506
495
 
507
- const UserProvider: React.FC<PropsWithChildren> = ({ children }) => {
508
- const [currentUser, setCurrentUser] = useState<TUser>(null);
496
+ That means your data will automatically be synchronized with the local storage, and you don't need to worry about losing the type of your Maps/Sets/Dates - the store takes care of it for you.
509
497
 
510
- // ...
498
+ # Extending Global Hooks
511
499
 
512
- return (
513
- <UserContext.Provider value={{ currentUser }}>
514
- {children}
515
- </UserContext.Provider>
516
- );
517
- };
500
+ Creating a custom builder for your **global hook** is made incredibly easy with the **createCustomGlobalState** function.
518
501
 
519
- // To don't overcomplicate the example let's just add but providers into this component, that will be enough
520
- const CountProvider: React.FC<PropsWithChildren> = ({ children }) => {
521
- const [count, setCount] = useState(0);
522
-
523
- const increase = () => setCount(count + 1);
524
- const decrease = () => setCount(count - 1);
525
-
526
- return (
527
- //one context is gonna share the edition of the state
528
- <CountContext.Provider value={{ count }}>
529
- {/* this second component will share the mutations of the state */}
530
- <CountContextSetter.Provider value={{ increase, decrease }}>
531
- {children}
532
- </CountContextSetter.Provider>
533
- </CountContext.Provider>
534
- );
535
- };
536
-
537
- // 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**
538
- const App = () => {
539
- return (
540
- <UserProvider>
541
- <CountProvider>
542
- {/* lets create two componets instead of one */}
543
- <ComponentSetter />
544
- <Component />
545
- </CountProvider>
546
- </UserProvider>
547
- );
548
- };
502
+ This function returns a new global state builder wrapped with the desired custom implementation, allowing you to get creative! Le'ts see and example:
549
503
 
550
- const ComponentSetter = () => {
551
- const { increase, decrease } = useContext(CountContextSetter);
504
+ ```ts
505
+ import { formatFromStore, formatToStore, createCustomGlobalState } = 'react-global-state-hooks'
552
506
 
553
- return (
554
- <>
555
- <button onPress={increase}>Increase</button>
556
- <button onPress={decrease}>Decrease</button>
557
- </>
558
- );
507
+ // Optional configuration available for the consumers of the builder
508
+ type HookConfig = {
509
+ asyncStorageKey?: string;
559
510
  };
560
511
 
561
- const Component = () => {
562
- const { currentUser } = useContext(UserContext);
563
-
564
- // finally we are able to get access to the new context...
565
- const { count } = useContext(CountContext);
566
-
567
- return (
568
- <>
569
- <label>{currentUser.name}</label>
570
- <label>{count}</label>
571
- </>
572
- );
512
+ // This is the base metadata that all the stores created from the builder will have.
513
+ type BaseMetadata = {
514
+ isAsyncStorageReady?: boolean;
573
515
  };
574
- ```
575
516
 
576
- Wow, a lot!!! just to be able to separate the mutations... and have mutations!!
517
+ export const createGlobalState = createCustomGlobalState<
518
+ BaseMetadata,
519
+ HookConfig
520
+ >({
521
+ /**
522
+ * This function executes immediately after the global state is created, before the invocations of the hook
523
+ */
524
+ onInitialize: async ({ setState, setMetadata }, config) => {
525
+ setMetadata((metadata) => ({
526
+ ...(metadata ?? {}),
527
+ isAsyncStorageReady: null,
528
+ }));
529
+
530
+ const asyncStorageKey = config?.asyncStorageKey;
531
+ if (!asyncStorageKey) return;
532
+
533
+ const storedItem = (await asyncStorage.getItem(asyncStorageKey)) as string;
534
+
535
+ // update the metadata, remember, metadata is not reactive
536
+ setMetadata((metadata) => ({
537
+ ...metadata,
538
+ isAsyncStorageReady: true,
539
+ }));
540
+
541
+ if (storedItem === null) {
542
+ return setState((state) => state, { forceUpdate: true });
543
+ }
544
+
545
+ const parsed = formatFromStore(storedItem, {
546
+ jsonParse: true,
547
+ });
548
+
549
+ setState(parsed, { forceUpdate: true });
550
+ },
577
551
 
578
- ### it would be easier with the GlobalStore? Let's see.
552
+ onChange: ({ getState }, config) => {
553
+ if (!config?.asyncStorageKey) return;
579
554
 
580
- ```tsx
581
- type TUser = {
582
- name: string;
583
- email: string;
584
- };
555
+ const state = getState();
585
556
 
586
- const useUser = new GlobalStore<TUser>({
587
- name: null,
588
- email: null,
589
- }).getHook();
590
-
591
- // let's modify the store to add custom actions, the second parameter is configuration let's just pass null for now
592
- const countStore = new GlobalStore(0, null, {
593
- increase() {
594
- return ({ setState }: StoreTools<number>) => {
595
- setState((state) => state + 1);
596
- };
597
- },
557
+ const formattedObject = formatToStore(state, {
558
+ stringify: true,
559
+ });
598
560
 
599
- decrease() {
600
- return ({ setState }: StoreTools<number>) => {
601
- setState((state) => state - 1);
602
- };
561
+ asyncStorage.setItem(config.asyncStorageKey, formattedObject);
603
562
  },
604
- } as const);
563
+ });
564
+ ```
605
565
 
606
- const useCount = countStore.getHook();
566
+ 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.
607
567
 
608
- // this actions don't use hooks, but are connected to the store and all the subscribers will be notified
609
- const [, countActions] = countStore.getHookDecoupled();
568
+ It is worth mentioning that the **onInitialize** function will be executed only once per global state.
610
569
 
611
- // this component is not subscribed to the store, so it will not be notified when the state changes
612
- const ComponentSetter = () => {
613
- return (
614
- <>
615
- <button onPress={countActions.increase}>Increase</button>
616
- <button onPress={countActions.decrease}>Decrease</button>
617
- </>
618
- );
619
- };
570
+ 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).
620
571
 
621
- // this component is subscribed to the store, so it will be notified when the state changes
622
- const Component = () => {
623
- const [user] = useUser();
624
- const [count, actions] = useCount();
572
+ Let's see how to create a global state using our new builder:
625
573
 
626
- return (
627
- <>
628
- <label>{count}</label>
629
- </>
630
- );
631
- };
574
+ ```ts
575
+ const useTodos = createGlobalState(new Map<string, number>(), {
576
+ config: {
577
+ asyncStorageKey: 'todos',
578
+ },
579
+ });
632
580
  ```
633
581
 
634
- ### So let's analyze what happened
582
+ That's correct! If you add an **asyncStorageKey** to the state configuration, the state will be synchronized with the **asyncStorage**
635
583
 
636
- To restrict the state manipulations with the custom actions, we just need to add a third parameter to the store.
584
+ Let's see how to use this async storage hook into our components:
637
585
 
638
586
  ```ts
639
- const countStore = new GlobalStore(0, null, {
640
- log: (action: string) => () => console.log(action),
587
+ const [todos, setTodos, metadata] = useTodos();
641
588
 
642
- // every action is a function that returns a function that receives the store tools
643
- increase() {
644
- return ({ setState, getState }: StoreTools<number>): number => {
645
- setState((state) => state + 1);
589
+ return (<>
590
+ {metadata.isAsyncStorageReady ? <TodoList todos={todos} /> : <Text>Loading...</Text>}
591
+ <>);
592
+ ```
646
593
 
647
- // actions are able to communicate between them
648
- this.log('increase');
594
+ The **metadata** is not reactive information and can only be modified from inside the global state lifecycle methods.
649
595
 
650
- return getState();
651
- };
652
- },
653
- } as const);
596
+ # Life cycle methods
597
+
598
+ There are some lifecycle methods available for use with global hooks, let's review them:
654
599
 
655
- // the const is necessary to avoid typescript errors
600
+ ```ts
601
+ /**
602
+ * @description callback function called when the store is initialized
603
+ * @returns {void} result - void
604
+ * */
605
+ onInit?: ({
606
+ /**
607
+ * Set the metadata
608
+ * @param {TMetadata} setter - The metadata or a function that will receive the metadata and return the new metadata
609
+ * */
610
+ setMetadata: MetadataSetter<TMetadata>;
611
+
612
+ /**
613
+ * Set the state
614
+ * @param {TState} setter - The state or a function that will receive the state and return the new state
615
+ * @param {{ forceUpdate?: boolean }} options - Options
616
+ * */
617
+ setState: StateSetter<TState>;
618
+
619
+ /**
620
+ * Get the state
621
+ * @returns {TState} result - The state
622
+ * */
623
+ getState: () => TState;
624
+
625
+ /**
626
+ * Get the metadata
627
+ * @returns {TMetadata} result - The metadata
628
+ * */
629
+ getMetadata: () => TMetadata;
630
+
631
+ /**
632
+ * Actions of the hook if configuration was provided
633
+ */
634
+ actions: TActions;
635
+ }: StateConfigCallbackParam<TState, TMetadata, TActions>) => void;
636
+
637
+ /**
638
+ * @description - callback function called every time the state is changed
639
+ */
640
+ onStateChanged?: (parameters: StateChangesParam<TState, TMetadata, TActions>) => void;
641
+
642
+ /**
643
+ * callback function called every time a component is subscribed to the store
644
+ */
645
+ onSubscribed?: (parameters: StateConfigCallbackParam<TState, TMetadata, TActions>) => void;
646
+
647
+ /**
648
+ * callback function called every time the state is about to change and it allows you to prevent the state change
649
+ */
650
+ computePreventStateChange?: (parameters: StateChangesParam<TState, TMetadata, TActions>) => boolean;
656
651
  ```
657
652
 
658
- All the library is strongly typed, we use generics to return the correct data type in each action.
653
+ You can pass this callbacks between on the second parameter of the builders like **createGlobalState**
659
654
 
660
655
  ```ts
661
- const [, actions] = countStore.getHookDecoupled();
656
+ const useData = createGlobalState(
657
+ { value: 1 },
658
+ {
659
+ metadata: {
660
+ someExtraInformation: 'someExtraInformation',
661
+ },
662
+ // onSubscribed: (StateConfigCallbackParam) => {},
663
+ // onInit // etc
664
+ computePreventStateChange: ({ state, previousState }) => {
665
+ const prevent = isEqual(state, previousState);
662
666
 
663
- // for example the type of actions.increase will be: () => number
664
- // just in case, even the parameters of the actions are gonna be exposed through TS
667
+ return prevent;
668
+ },
669
+ }
670
+ );
665
671
  ```
666
672
 
667
- ## getHookDecoupled
673
+ 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.
668
674
 
669
- ### **getHookDecoupled** returns a tuple with the state and the actions,
675
+ Let's see an example again with the **asyncStorage** custom global hook but with the abstract class.
670
676
 
671
- This is so useful when you want to use the actions without having to be subscribed to changes of the state.
672
- There is also a third element in the tuple which is a function for getting the metadata of the store
677
+ ```ts
678
+ export class GlobalStore<
679
+ TState,
680
+ TMetadata extends {
681
+ asyncStorageKey?: string;
682
+ isAsyncStorageReady?: boolean;
683
+ } | null = null,
684
+ TStateSetter extends
685
+ | ActionCollectionConfig<TState, TMetadata>
686
+ | StateSetter<TState> = StateSetter<TState>
687
+ > extends GlobalStoreAbstract<TState, TMetadata, TStateSetter> {
688
+ constructor(
689
+ state: TState,
690
+ config: GlobalStoreConfig<TState, TMetadata, TStateSetter> = {},
691
+ actionsConfig: TStateSetter | null = null
692
+ ) {
693
+ super(state, config, actionsConfig);
694
+
695
+ this.initialize();
696
+ }
697
+
698
+ protected onInitialize = async ({
699
+ setState,
700
+ setMetadata,
701
+ getMetadata,
702
+ getState,
703
+ }: StateConfigCallbackParam<TState, TMetadata, TStateSetter>) => {
704
+ setMetadata({
705
+ ...(metadata ?? {}),
706
+ isAsyncStorageReady: null,
707
+ });
708
+
709
+ const metadata = getMetadata();
710
+ const asyncStorageKey = metadata?.asyncStorageKey;
711
+
712
+ if (!asyncStorageKey) return;
713
+
714
+ const storedItem = (await asyncStorage.getItem(asyncStorageKey)) as string;
715
+ setMetadata({
716
+ ...metadata,
717
+ isAsyncStorageReady: true,
718
+ });
719
+
720
+ if (storedItem === null) {
721
+ const state = getState();
722
+
723
+ // force the re-render of the subscribed components even if the state is the same
724
+ return setState(state, { forceUpdate: true });
725
+ }
726
+
727
+ const items = formatFromStore<TState>(storedItem, {
728
+ jsonParse: true,
729
+ });
730
+
731
+ setState(items, { forceUpdate: true });
732
+ };
733
+
734
+ protected onChange = ({
735
+ getMetadata,
736
+ getState,
737
+ }: StateChangesParam<TState, TMetadata, NonNullable<TStateSetter>>) => {
738
+ const asyncStorageKey = getMetadata()?.asyncStorageKey;
739
+
740
+ if (!asyncStorageKey) return;
673
741
 
674
- ### the metadata of the store is not reactive information which could be shared through the store
742
+ const state = getState();
675
743
 
676
- ## Adding metadata to the store
744
+ const formattedObject = formatToStore(state, {
745
+ stringify: true,
746
+ });
677
747
 
678
- ```tsx
679
- const [, , getMetadata] = new GlobalStore(0, {
748
+ asyncStorage.setItem(asyncStorageKey, formattedObject);
749
+ };
750
+ }
751
+ ```
752
+
753
+ Then, from an instance of the global store, you will be able to access the hooks.
754
+
755
+ ```ts
756
+ const storage = new GlobalStore(0, {
680
757
  metadata: {
681
- isStoredSyncronized: false,
758
+ asyncStorageKey: 'counter',
759
+ isAsyncStorageReady: false,
682
760
  },
683
- }).getHookDecoupled();
761
+ });
684
762
 
685
- console.log(getMetadata().isStoredSyncronized); // false
763
+ const [getState, _, getMetadata] = storage.getHookDecoupled();
764
+ const useState = storage.getHook();
686
765
  ```
687
766
 
688
- The setMetadata is part of the store tools, so it can be used in the actions, but againg the metadata is not reactive!! so it will not trigger a re-render on the subscribers
689
-
690
- ...
691
-
692
- ...
767
+ ### **Note**: The GlobalStore class is still available in the package in case you were already extending from it.
693
768
 
694
769
  # That's it for now!! hope you enjoy coding!!