react-global-state-hooks 14.0.2 → 14.1.0-beta.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/GlobalStore.d.ts CHANGED
@@ -1,19 +1,21 @@
1
1
  import type { ActionCollectionConfig, ActionCollectionResult, AnyFunction, BaseMetadata, GlobalStoreCallbacks, StateChanges, StoreTools } from 'react-hooks-global-states/types';
2
2
  import { GlobalStoreAbstract } from 'react-hooks-global-states/GlobalStoreAbstract';
3
- import { LocalStorageConfig } from './types';
3
+ import type { LocalStorageConfig } from './types';
4
4
  export declare class GlobalStore<State, Metadata extends BaseMetadata, ActionsConfig extends ActionCollectionConfig<State, Metadata> | undefined | unknown, PublicStateMutator = keyof ActionsConfig extends never | undefined ? React.Dispatch<React.SetStateAction<State>> : ActionCollectionResult<State, Metadata, NonNullable<ActionsConfig>>> extends GlobalStoreAbstract<State, Metadata, ActionsConfig> {
5
- protected localStorage: LocalStorageConfig | null;
5
+ protected localStorage: LocalStorageConfig<State> | null;
6
6
  constructor(state: State);
7
7
  constructor(state: State, args: {
8
8
  metadata?: Metadata;
9
9
  callbacks?: GlobalStoreCallbacks<State, PublicStateMutator extends AnyFunction ? null : PublicStateMutator, Metadata>;
10
10
  actions?: ActionsConfig;
11
11
  name?: string;
12
- localStorage?: LocalStorageConfig;
12
+ localStorage?: LocalStorageConfig<State>;
13
13
  });
14
- protected isLocalStorageAvailable: (config: LocalStorageConfig | null) => config is LocalStorageConfig;
15
- protected _onInitialize: ({ setState, getState, }: StoreTools<State, PublicStateMutator, Metadata>) => void;
16
- protected _onChange: ({ getState, }: StoreTools<State, PublicStateMutator, Metadata> & StateChanges<State>) => void;
14
+ protected isGlobalLocalStorageAvailable: () => boolean;
15
+ protected _onInitialize: ({ getState }: StoreTools<State, PublicStateMutator, Metadata>) => void;
16
+ private trySetLocalStorageItem;
17
+ private updateStateWithValidation;
18
+ protected _onChange: ({ state, }: StoreTools<State, PublicStateMutator, Metadata> & StateChanges<State>) => void;
17
19
  /**
18
20
  * We set it to null so the instances of the GlobalStoreAbstract can override it.
19
21
  */
@@ -25,5 +27,8 @@ export declare class GlobalStore<State, Metadata extends BaseMetadata, ActionsCo
25
27
  */
26
28
  protected onInit: (parameters: StoreTools<State, PublicStateMutator, Metadata>) => void;
27
29
  protected onStateChanged: (args: StoreTools<State, PublicStateMutator, Metadata> & StateChanges<State>) => void;
30
+ private getLocalStorageItem;
31
+ private setLocalStorageItem;
32
+ private handleLocalStorageError;
28
33
  }
29
34
  export default GlobalStore;
package/GlobalStore.js CHANGED
@@ -1 +1 @@
1
- var t,e;t=this,e=(t,e,o)=>(()=>{"use strict";var r={266:e=>{e.exports=t},683:t=>{t.exports=e},959:t=>{t.exports=o}},a={};function n(t){var e=a[t];if(void 0!==e)return e.exports;var o=a[t]={exports:{}};return r[t](o,o.exports,n),o.exports}var l={};return(()=>{var t=l;function e(t){return e="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},e(t)}function o(t,o){if(o&&("object"==e(o)||"function"==typeof o))return o;if(void 0!==o)throw new TypeError("Derived constructors may only return object or undefined");return function(t){if(void 0===t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return t}(t)}function r(){try{var t=!Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],function(){}))}catch(t){}return(r=function(){return!!t})()}function a(t){return a=Object.setPrototypeOf?Object.getPrototypeOf.bind():function(t){return t.__proto__||Object.getPrototypeOf(t)},a(t)}function c(t,e){return c=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(t,e){return t.__proto__=e,t},c(t,e)}Object.defineProperty(t,"__esModule",{value:!0}),t.GlobalStore=void 0;var i=n(266),u=n(959),s=n(683),f=function(t){function e(t){var n,l,c,i,f,b=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{metadata:{}};return function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}(this,e),c=this,f=[t,b],i=a(i=e),(n=o(c,r()?Reflect.construct(i,f||[],a(c).constructor):i.apply(c,f))).localStorage=null,n.isLocalStorageAvailable=function(t){return Boolean((null==t?void 0:t.key)&&(null===globalThis||void 0===globalThis?void 0:globalThis.localStorage))},n._onInitialize=function(t){var e=t.setState,o=t.getState;if(n.isLocalStorageAvailable(n.localStorage)){var r=(0,u.getLocalStorageItem)(n.localStorage);if(null===r){var a=o();return(0,s.setLocalStorageItem)(a,n.localStorage)}e(r)}},n._onChange=function(t){var e=t.getState;n.isLocalStorageAvailable(n.localStorage)&&(0,s.setLocalStorageItem)(e(),n.localStorage)},n.onInitialize=null,n.onChange=null,n.onInit=function(t){var e;null===(e=n._onInitialize)||void 0===e||e.call(n,t)},n.onStateChanged=function(t){var e;null===(e=n._onChange)||void 0===e||e.call(n,t)},n.localStorage=null!==(l=b.localStorage)&&void 0!==l?l:null,n.constructor!==e?o(n):(n.initialize(),n)}return function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function");t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,writable:!0,configurable:!0}}),Object.defineProperty(t,"prototype",{writable:!1}),e&&c(t,e)}(e,t),n=e,Object.defineProperty(n,"prototype",{writable:!1}),n;var n}(i.GlobalStoreAbstract);t.GlobalStore=f,t.default=f})(),l})(),"object"==typeof exports&&"object"==typeof module?module.exports=e(require("react-hooks-global-states/GlobalStoreAbstract"),require("./setLocalStorageItem.js"),require("./getLocalStorageItem.js")):"function"==typeof define&&define.amd?define(["react-hooks-global-states/GlobalStoreAbstract","./setLocalStorageItem.js","./getLocalStorageItem.js"],e):"object"==typeof exports?exports["react-global-state-hooks"]=e(require("react-hooks-global-states/GlobalStoreAbstract"),require("./setLocalStorageItem.js"),require("./getLocalStorageItem.js")):t["react-global-state-hooks"]=e(t["react-hooks-global-states/GlobalStoreAbstract"],t["./setLocalStorageItem.js"],t["./getLocalStorageItem.js"]);
1
+ var t,o;t=this,o=(t,o,e,r)=>(()=>{"use strict";var a={70:(t,o)=>{Object.defineProperty(o,"__esModule",{value:!0}),o.default=function(t){try{return{result:t(),error:null}}catch(t){return{result:null,error:t}}}},266:o=>{o.exports=t},330:t=>{t.exports=o},413:t=>{t.exports=e},773:t=>{t.exports=r}},l={};function n(t){var o=l[t];if(void 0!==o)return o.exports;var e=l[t]={exports:{}};return a[t](e,e.exports,n),e.exports}var i={};return(()=>{var t=i;function o(t){return o="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},o(t)}function e(t,e){if(e&&("object"==o(e)||"function"==typeof e))return e;if(void 0!==e)throw new TypeError("Derived constructors may only return object or undefined");return function(t){if(void 0===t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return t}(t)}function r(){try{var t=!Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],function(){}))}catch(t){}return(r=function(){return!!t})()}function a(t){return a=Object.setPrototypeOf?Object.getPrototypeOf.bind():function(t){return t.__proto__||Object.getPrototypeOf(t)},a(t)}function l(t,o){return l=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(t,o){return t.__proto__=o,t},l(t,o)}var u=function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(t,"__esModule",{value:!0}),t.GlobalStore=void 0;var c=n(266),s=u(n(70)),f=u(n(330)),d=u(n(413)),g=u(n(773)),v=function(t){function o(t){var l,n,i,u,c,v=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{metadata:{}};return function(t,o){if(!(t instanceof o))throw new TypeError("Cannot call a class as a function")}(this,o),i=this,c=[t,v],u=a(u=o),(l=e(i,r()?Reflect.construct(u,c||[],a(i).constructor):u.apply(i,c))).localStorage=null,l.isGlobalLocalStorageAvailable=function(){var t;return Boolean((null===(t=l.localStorage)||void 0===t?void 0:t.key)&&(null===globalThis||void 0===globalThis?void 0:globalThis.localStorage))},l._onInitialize=function(t){var o=t.getState,e=l.localStorage;if(e&&l.isGlobalLocalStorageAvailable()){var r=e.versioning,a=(0,s.default)(function(){var t,o,a=null===(t=e.adapter)||void 0===t?void 0:t.getItem;return a?{s:a(e.key),v:null!==(o=null==r?void 0:r.version)&&void 0!==o?o:-1}:l.getLocalStorageItem()}),n=a.result,i=a.error;if(i)return l.handleLocalStorageError(i),void l.updateStateWithValidation(l.getState());if(n){var u=n.v===(null==r?void 0:r.version),c=null==r?void 0:r.migrator;if(u||!c||e.adapter)l.updateStateWithValidation(n.s);else{var f=(0,s.default)(function(){return c({legacy:n.s,initial:l.getState()})}),d=f.result,g=f.error;g?(l.handleLocalStorageError(g),l.updateStateWithValidation(l.getState())):l.updateStateWithValidation(d)}}else l.updateStateWithValidation(o())}},l.trySetLocalStorageItem=function(t){var o=l.localStorage;if(o){var e=(0,s.default)(function(){var e,r=null===(e=o.adapter)||void 0===e?void 0:e.setItem;if(!r)return l.setLocalStorageItem(t);r(o.key,t)}),r=e.error;r&&l.handleLocalStorageError(r)}},l.updateStateWithValidation=function(t){var o=l.localStorage;if(o){var e=(0,s.default)(function(){return o.validator({restored:t,initial:l.getState()})}),r=e.result,a=e.error;if(a)return l.handleLocalStorageError(a),void l.trySetLocalStorageItem(l.getState());l.setState(r),l.trySetLocalStorageItem(r)}},l._onChange=function(t){var o=t.state,e=l.localStorage;if(e&&l.isGlobalLocalStorageAvailable()){var r=(0,s.default)(function(){var t,r=null===(t=e.adapter)||void 0===t?void 0:t.setItem;if(r)return r(e.key,o);l.setLocalStorageItem(o)}),a=r.error;a&&l.handleLocalStorageError(a)}},l.onInitialize=null,l.onChange=null,l.onInit=function(t){var o;null===(o=l._onInitialize)||void 0===o||o.call(l,t)},l.onStateChanged=function(t){var o;null===(o=l._onChange)||void 0===o||o.call(l,t)},l.getLocalStorageItem=function(){var t=globalThis.localStorage.getItem(l.localStorage.key);return(0,g.default)(t)?null:(0,f.default)(t)},l.setLocalStorageItem=function(t){var o,e,r,a={s:t,v:null!==(r=null===(e=null===(o=l.localStorage)||void 0===o?void 0:o.versioning)||void 0===e?void 0:e.version)&&void 0!==r?r:-1},n=(0,d.default)(a);globalThis.localStorage.setItem(l.localStorage.key,n)},l.handleLocalStorageError=function(t){var o;if(null===(o=l.localStorage)||void 0===o?void 0:o.onError)return l.localStorage.onError(t)},l.localStorage=null!==(n=v.localStorage)&&void 0!==n?n:null,l.constructor!==o?e(l):(l.initialize(),l)}return function(t,o){if("function"!=typeof o&&null!==o)throw new TypeError("Super expression must either be null or a function");t.prototype=Object.create(o&&o.prototype,{constructor:{value:t,writable:!0,configurable:!0}}),Object.defineProperty(t,"prototype",{writable:!1}),o&&l(t,o)}(o,t),n=o,Object.defineProperty(n,"prototype",{writable:!1}),n;var n}(c.GlobalStoreAbstract);t.GlobalStore=v,t.default=v})(),i})(),"object"==typeof exports&&"object"==typeof module?module.exports=o(require("react-hooks-global-states/GlobalStoreAbstract"),require("json-storage-formatter/formatFromStore"),require("json-storage-formatter/formatToStore"),require("json-storage-formatter/isNil")):"function"==typeof define&&define.amd?define(["react-hooks-global-states/GlobalStoreAbstract","json-storage-formatter/formatFromStore","json-storage-formatter/formatToStore","json-storage-formatter/isNil"],o):"object"==typeof exports?exports["react-global-state-hooks"]=o(require("react-hooks-global-states/GlobalStoreAbstract"),require("json-storage-formatter/formatFromStore"),require("json-storage-formatter/formatToStore"),require("json-storage-formatter/isNil")):t["react-global-state-hooks"]=o(t["react-hooks-global-states/GlobalStoreAbstract"],t["json-storage-formatter/formatFromStore"],t["json-storage-formatter/formatToStore"],t["json-storage-formatter/isNil"]);
package/README.md CHANGED
@@ -13,67 +13,36 @@ Effortless **global state management** for `React` & `React Native` and `Preact`
13
13
 
14
14
  Works seamlessly with **React & React Native**:
15
15
 
16
- - **[react-hooks-global-states](https://www.npmjs.com/package/react-hooks-global-states)** compatible with both `React & React Native`
17
16
  - **[react-global-state-hooks](https://www.npmjs.com/package/react-global-state-hooks)** specific for web applications (**local-storage integration**).
18
17
  - **[react-native-global-state-hooks](https://www.npmjs.com/package/react-native-global-state-hooks)** specific for React Native projects (**async-storage integration**).
19
18
 
20
19
  ---
21
20
 
22
- ## 🚀 React Hooks Global States - DevTools Extension
23
-
24
- React Hooks Global States includes a dedicated, `devTools extension` to streamline your development workflow! Easily visualize, inspect, debug, and modify your application's global state in real-time right within your browser.
25
-
26
- ### 🔗 [Install the DevTools Extension for Chrome](https://chromewebstore.google.com/detail/bafojplmkpejhglhjpibpdhoblickpee/preview?hl=en&authuser=0)
27
-
28
- ### 📸 DevTools Highlights
29
-
30
- | **Track State Changes** | **Modify the State** |
31
- | ------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
32
- | ![Track State Changes](https://github.com/johnny-quesada-developer/react-hooks-global-states/raw/main/public/track-state-changes.png) | ![Modify the State](https://github.com/johnny-quesada-developer/react-hooks-global-states/raw/main/public/modify-the-state.png) |
33
- | Effortlessly monitor state updates and history. | Instantly edit global states directly from the extension. |
34
-
35
- ---
36
-
37
- | **Restore the State** | **Custom Actions Granularity** |
38
- | --------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
39
- | ![Restore the State](https://github.com/johnny-quesada-developer/react-hooks-global-states/raw/main/public/restore-the-state.png) | ![Custom Actions Granularity](https://github.com/johnny-quesada-developer/react-hooks-global-states/raw/main/public/custom-actions-granularity.png) |
40
- | Quickly revert your application to a previous state. | Precisely debug specific actions affecting state changes. |
41
-
42
- <br>
43
-
44
- ## 🗂️ Persisting State with LocalStorage
45
-
46
- To persist the global state using **LocalStorage**, simply add the `localStorage` option:
47
-
48
- ```tsx
49
- const useContacts = createGlobalState(new Map(), {
50
- localStorage: {
51
- key: 'contacts',
52
- },
53
- });
54
- ```
55
-
56
21
  ## 🛠 Creating a Global State
57
22
 
58
- Define a **global state** in **one line**:
59
-
60
23
  ```tsx
61
- import { createGlobalState } from 'react-hooks-global-states/createGlobalState';
24
+ import createGlobalState from 'react-hooks-global-states/createGlobalState';
25
+
62
26
  export const useCount = createGlobalState(0);
63
27
  ```
64
28
 
65
- Now, use it inside a component:
29
+ `useCount` will work as a regular `useState` hook, but the state will persist across your entire app!
66
30
 
67
31
  ```tsx
32
+ // Component A
68
33
  const [count, setCount] = useCount();
34
+
69
35
  return <Button onClick={() => setCount((count) => count + 1)}>{count}</Button>;
70
- ```
71
36
 
72
- Works just like **useState**, but the **state is shared globally**! 🎉
37
+ // Component B
38
+ const [count] = useCount();
39
+
40
+ return <Text>Count is: {count}</Text>;
41
+ ```
73
42
 
74
43
  ---
75
44
 
76
- ## 🎯 Selectors: Subscribing to Specific State Changes
45
+ ## 🎯 Selectors
77
46
 
78
47
  For **complex state objects**, you can subscribe to specific properties instead of the entire state:
79
48
 
@@ -84,7 +53,9 @@ export const useContacts = createGlobalState({ entities: [], selected: new Set<n
84
53
  To access only the `entities` property:
85
54
 
86
55
  ```tsx
87
- const [contacts] = useContacts((state) => state.entities);
56
+ const [filter, setFilter] = useContacts((state) => state.filter);
57
+ const contacts = useContacts.select((state) => state.entities, [filter]);
58
+
88
59
  return (
89
60
  <ul>
90
61
  {contacts.map((contact) => (
@@ -94,34 +65,20 @@ return (
94
65
  );
95
66
  ```
96
67
 
97
- ### 📌 Using Dependencies in Selectors
98
-
99
- You can also add **dependencies** to a selector. This is useful when you want to derive state based on another piece of state (e.g., a filtered list). For example, if you're filtering contacts based on a `filter` value:
68
+ Alternatively, you can also define the `isEqual` and `isEqualRoot` options to optimize re-selection:
100
69
 
101
70
  ```tsx
102
- const [contacts] = useContacts(
103
- (state) => state.entities.filter((item) => item.name.includes(filter)),
104
- [filter],
105
- );
106
- ```
107
-
108
- Alternatively, you can pass dependencies inside an **options object**:
109
-
110
- ```tsx
111
- const [contacts] = useContacts((state) => state.entities.filter((item) => item.name.includes(filter)), {
71
+ const contacts = useContacts.select((state) => state.entities.filter((item) => item.name.includes(filter)), {
112
72
  dependencies: [filter],
73
+ isEqual: (a, b) => a.length === b.length,
113
74
  isEqualRoot: (a, b) => a.entities === b.entities,
114
75
  });
115
76
  ```
116
77
 
117
- Unlike Redux, where only **root state changes trigger re-selection**, this approach ensures that **derived values recompute when dependencies change** while maintaining performance.
118
-
119
78
  ---
120
79
 
121
80
  ## 🔄 Reusing Selectors
122
81
 
123
- ### 📌 Creating a Selector
124
-
125
82
  ```tsx
126
83
  export const useContactsArray = useContacts.createSelectorHook((state) => state.entities);
127
84
  export const useContactsCount = useContactsArray.createSelectorHook((entities) => entities.length);
@@ -130,30 +87,38 @@ export const useContactsCount = useContactsArray.createSelectorHook((entities) =
130
87
  ### 📌 Using Selectors in Components
131
88
 
132
89
  ```tsx
133
- const [contacts] = useContactsArray();
134
- const [count] = useContactsCount();
90
+ const contacts = useContactsArray();
91
+ const count = useContactsCount();
135
92
  ```
136
93
 
137
- #### ✅ Selectors support inline selectors and dependencies
138
-
139
- You can still **use dependencies** inside a selector hook:
94
+ #### ✅ Inline Selectors
140
95
 
141
96
  ```tsx
142
- const [filteredContacts] = useContactsArray(
97
+ const filteredContacts = useContactsArray(
143
98
  (contacts) => contacts.filter((c) => c.name.includes(filter)),
144
99
  [filter],
145
100
  );
146
101
  ```
147
102
 
148
- #### ✅ Selector hooks share the same state mutator
103
+ ---
104
+
105
+ ## 🗂️ Persisting State with LocalStorage
149
106
 
150
- The **stateMutator remains the same** across all derived selectors, meaning actions and setState functions stay consistent.
107
+ To persist the global state using **LocalStorage**, simply add the `localStorage` option:
151
108
 
152
109
  ```tsx
153
- const [actions1] = useContactsArray();
154
- const [actions2] = useContactsCount();
110
+ const useContacts = createGlobalState(new Map(), {
111
+ localStorage: {
112
+ key: 'contacts',
155
113
 
156
- console.log(actions1 === actions2); // true
114
+ // validator is mandatory to prevent corrupted data from populating the state
115
+ validator: ({ restored, initial }) => {
116
+ // validate the restored value
117
+ if (!isMap(restored)) return initial;
118
+ return restored as typeof initial;
119
+ },
120
+ },
121
+ });
157
122
  ```
158
123
 
159
124
  ---
@@ -163,47 +128,78 @@ console.log(actions1 === actions2); // true
163
128
  Restrict **state modifications** by defining custom actions:
164
129
 
165
130
  ```tsx
166
- export const useContacts = createGlobalState(
167
- { filter: '', items: [] },
168
- {
169
- actions: {
170
- async fetch() {
171
- return async ({ setState }) => {
172
- const items = await fetchItems();
173
- setState({ items });
174
- };
175
- },
176
- setFilter(filter: string) {
177
- return ({ setState }) => {
178
- setState((state) => ({ ...state, filter }));
179
- };
180
- },
131
+ const initialValue = {
132
+ filter: '',
133
+ contacts: [] as Array<{ id: number; name: string; email?: string }>,
134
+ status: 'idle' as 'idle' | 'loading' | 'success' | 'error',
135
+ };
136
+
137
+ /** *
138
+ * For complex state, prefer a semantic, declarative store.
139
+ * For example let's add the suffix `$` which is a common convention to denote stores.
140
+ */
141
+ export const contacts$ = createGlobalState(initialValue, {
142
+ actions: {
143
+ async fetch() {
144
+ return async ({ setState }) => {
145
+ setState((s) => ({ ...s, status: 'loading' }));
146
+
147
+ const contacts = await fetchItems();
148
+
149
+ setState((s) => ({ ...s, contacts, status: 'success' }));
150
+ };
151
+ },
152
+
153
+ setFilter(filter: string) {
154
+ return ({ setState }) => {
155
+ setState((s) => ({ ...s, filter }));
156
+ };
181
157
  },
182
158
  },
183
- );
159
+ });
184
160
  ```
185
161
 
186
- Now, instead of `setState`, the hook returns **actions**:
162
+ Then inside your components, you can use your global hook as a store:
187
163
 
188
164
  ```tsx
189
- const [filter, { setFilter }] = useContacts();
165
+ // Component A
166
+ // Subscribe to changes of the contacts property
167
+ const contacts = contacts$.use.select((s) => s.contacts);
168
+
169
+ // Component B
170
+ const onChangeFilter = (newFilter: string) => {
171
+ // Directly use the actions from the store without subscribing to state changes
172
+ contacts$.actions.setFilter(newFilter);
173
+ };
174
+
175
+ // Other examples
176
+ const useReusableSelector = contacts$.createSelectorHook((s) => s.filter);
177
+ const observable = contacts$.createObservable((s) => `Status is: \${s.status}`);
178
+ const metadata = contacts$.getMetadata(); // non-reactive metadata
190
179
  ```
191
180
 
192
181
  ---
193
182
 
194
183
  ## 🌍 Accessing Global State Outside Components
195
184
 
196
- Use `stateControls()` to **retrieve or update state outside React components**:
185
+ As mentioned earlier, you can use the store actions outside components by directly calling actions and dispatchers from the store.
197
186
 
198
187
  ```tsx
199
- const [contactsRetriever, contactsApi] = useContacts.stateControls();
200
- console.log(contactsRetriever()); // Retrieves the current state
188
+ /**
189
+ * contacts$.actions has access to all the defined actions of the store
190
+ * @example:
191
+ */
192
+ useEffect(() => {
193
+ contacts$.actions.fetch();
194
+ }, []);
195
+
196
+ console.log(contacts$.getState()); // Retrieves the current state without subscription
201
197
  ```
202
198
 
203
- #### ✅ Subscribe to changes
199
+ #### ✅ Subscribe to changes outside components
204
200
 
205
201
  ```tsx
206
- const unsubscribe = contactsRetriever((state) => {
202
+ const unsubscribe = contacts$.subscribe((state) => {
207
203
  console.log('State updated:', state);
208
204
  });
209
205
  ```
@@ -211,13 +207,19 @@ const unsubscribe = contactsRetriever((state) => {
211
207
  #### ✅ Subscriptions are great when one state depends on another.
212
208
 
213
209
  ```tsx
214
- const useSelectedContact = createGlobalState(null, {
210
+ const initialValue = null as string | null;
211
+
212
+ const selectedId$ = createGlobalState(initialValue, {
215
213
  callbacks: {
216
214
  onInit: ({ setState, getState }) => {
217
- contactsRetriever(
218
- (state) => state.contacts,
215
+ /**
216
+ * Let's create a subscription to contacts$ to clear the selectedId if the contact is removed
217
+ */
218
+ contacts$.subscribe(
219
+ (state) => state.contacts, // listen only to contacts changes
219
220
  (contacts) => {
220
- if (!contacts.has(getState())) setState(null);
221
+ const hasContactId = contacts.has(getState());
222
+ if (!hasContactId) setState(null);
221
223
  },
222
224
  );
223
225
  },
@@ -229,66 +231,26 @@ const useSelectedContact = createGlobalState(null, {
229
231
 
230
232
  ## 🎭 Using Context for Scoped State
231
233
 
232
- - **Scoped State** – Context state is **isolated inside the provider**.
233
- - **Same API** – Context supports **selectors, actions, and state controls**.
234
-
235
234
  ### 📌 Creating a Context
236
235
 
237
236
  ```tsx
238
237
  import { createContext } from 'react-global-state-hooks/createContext';
239
- export const [useCounterContext, CounterProvider] = createContext(0);
238
+
239
+ export const counter$ = createContext(0);
240
240
  ```
241
241
 
242
242
  Wrap your app:
243
243
 
244
244
  ```tsx
245
- <CounterProvider>
245
+ <counter$.Provider>
246
246
  <MyComponent />
247
- </CounterProvider>
247
+ </counter$.Provider>
248
248
  ```
249
249
 
250
250
  Use the context state:
251
251
 
252
252
  ```tsx
253
- const [count] = useCounterContext();
254
- ```
255
-
256
- ### 📌 Context Selectors
257
-
258
- Works **just like global state**, but within the provider.
259
-
260
- ---
261
-
262
- ## 🔥 Observables: Watching State Changes
263
-
264
- Observables **let you react to state changes** via subscriptions.
265
-
266
- ### 📌 Creating an Observable
267
-
268
- ```tsx
269
- export const useCounter = createGlobalState(0);
270
- export const counterLogs = useCounter.createObservable((count) => `Counter is at ${count}`);
271
- ```
272
-
273
- ### 📌 Subscribing to an Observable
274
-
275
- ```tsx
276
- const unsubscribe = counterLogs((message) => {
277
- console.log(message);
278
- });
279
- ```
280
-
281
- ### 📌 Using Observables Inside Context
282
-
283
- ```tsx
284
- export const [useStateControls, useObservableBuilder] = useCounterContext.stateControls();
285
- const createObservable = useObservableBuilder();
286
- useEffect(() => {
287
- const unsubscribe = createObservable((count) => {
288
- console.log(`Updated count: ${count}`);
289
- });
290
- return unsubscribe;
291
- }, []);
253
+ const [count, setCount] = counter$.use();
292
254
  ```
293
255
 
294
256
  ---
@@ -298,14 +260,15 @@ useEffect(() => {
298
260
  | Feature | `createGlobalState` | `createContext` |
299
261
  | ---------------------- | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
300
262
  | **Scope** | Available globally across the entire app | Scoped to the Provider where it’s used |
301
- | **How to Use** | `const useCount = createGlobalState(0)` | `const [useCountContext, Provider] = createContext(0)` |
302
- | **createSelectorHook** | `useCount.createSelectorHook` | `useCountContext.createSelectorHook` |
263
+ | **How to Use** | `const useCount = createGlobalState(0)` | `const counter$ = createContext(0)` |
264
+ | **createSelectorHook** | `useCount.createSelectorHook` | `counter$.createSelectorHook` |
303
265
  | **inline selectors?** | ✅ Supported | ✅ Supported |
304
266
  | **Custom Actions** | ✅ Supported | ✅ Supported |
305
- | **Observables** | `useCount.createObservable` | `const [, useObservableBuilder] = useCountContext.stateControls()` |
306
- | **State Controls** | `useCount.stateControls()` | `const [useStateControls] = useCountContext.stateControls()` |
267
+ | **Observables** | `useCount.createObservable` | `counter$ = counter.use.observable()` |
307
268
  | **Best For** | Global app state (auth, settings, cache) | Scoped module state, reusable component state, or state shared between child components without being fully global |
308
269
 
270
+ ---
271
+
309
272
  ## 🔄 Lifecycle Methods
310
273
 
311
274
  Global state hooks support lifecycle callbacks for additional control.
@@ -331,9 +294,11 @@ const useData = createGlobalState(
331
294
 
332
295
  Use **`onInit`** for setup, **`onStateChanged`** to listen to updates, and **`computePreventStateChange`** to prevent unnecessary updates.
333
296
 
297
+ ---
298
+
334
299
  ## Metadata
335
300
 
336
- There is a possibility to add non reactive information in the global state:
301
+ There is a possibility to add non-reactive information in the global state:
337
302
 
338
303
  ```tsx
339
304
  const useCount = createGlobalState(0, { metadata: { renders: 0 } });
@@ -347,6 +312,30 @@ const [count, , metadata] = useCount();
347
312
  metadata.renders += 1;
348
313
  ```
349
314
 
315
+ ---
316
+
317
+ ## 🚀 React Hooks Global States - DevTools Extension
318
+
319
+ React Hooks Global States includes a dedicated, `devTools extension` to streamline your development workflow! Easily visualize, inspect, debug, and modify your application's global state in real-time right within your browser.
320
+
321
+ ### 🔗 [Install the DevTools Extension for Chrome](https://chromewebstore.google.com/detail/bafojplmkpejhglhjpibpdhoblickpee/preview?hl=en&authuser=0)
322
+
323
+ ### 📸 DevTools Highlights
324
+
325
+ | **Track State Changes** | **Modify the State** |
326
+ | ------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
327
+ | ![Track State Changes](https://github.com/johnny-quesada-developer/react-hooks-global-states/raw/main/public/track-state-changes.png) | ![Modify the State](https://github.com/johnny-quesada-developer/react-hooks-global-states/raw/main/public/modify-the-state.png) |
328
+ | Effortlessly monitor state updates and history. | Instantly edit global states directly from the extension. |
329
+
330
+ ---
331
+
332
+ | **Restore the State** | **Custom Actions Granularity** |
333
+ | --------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
334
+ | ![Restore the State](https://github.com/johnny-quesada-developer/react-hooks-global-states/raw/main/public/restore-the-state.png) | ![Custom Actions Granularity](https://github.com/johnny-quesada-developer/react-hooks-global-states/raw/main/public/custom-actions-granularity.png) |
335
+ | Quickly revert your application to a previous state. | Precisely debug specific actions affecting state changes. |
336
+
337
+ <br>
338
+
350
339
  ## 🎯 Ready to Try It?
351
340
 
352
341
  📦 **NPM Package:** [react-hooks-global-states](https://www.npmjs.com/package/react-hooks-global-states)
package/bundle.js CHANGED
@@ -1 +1 @@
1
- var e,t;e=this,t=(e,t,r,o,a,n,l,s,i,c)=>(()=>{"use strict";var u={78:t=>{t.exports=e},361:e=>{e.exports=t},487:e=>{e.exports=r},623:e=>{e.exports=o},670:e=>{e.exports=a},673:e=>{e.exports=n},683:e=>{e.exports=l},778:e=>{e.exports=s},804:e=>{e.exports=i},959:e=>{e.exports=c}},b={};function j(e){var t=b[e];if(void 0!==t)return t.exports;var r=b[e]={exports:{}};return u[e](r,r.exports,j),r.exports}var g={};return(()=>{var e=g;Object.defineProperty(e,"__esModule",{value:!0}),e.setLocalStorageItem=e.getLocalStorageItem=e.createGlobalState=e.GlobalStoreAbstract=e.GlobalStore=e.createContext=e.isRecord=e.throwWrongKeyOnActionCollectionConfig=e.uniqueId=e.shallowCompare=void 0;var t=j(673);Object.defineProperty(e,"shallowCompare",{enumerable:!0,get:function(){return t.shallowCompare}});var r=j(78);Object.defineProperty(e,"uniqueId",{enumerable:!0,get:function(){return r.uniqueId}});var o=j(361);Object.defineProperty(e,"throwWrongKeyOnActionCollectionConfig",{enumerable:!0,get:function(){return o.throwWrongKeyOnActionCollectionConfig}});var a=j(487);Object.defineProperty(e,"isRecord",{enumerable:!0,get:function(){return a.isRecord}});var n=j(623);Object.defineProperty(e,"createContext",{enumerable:!0,get:function(){return n.createContext}});var l=j(778);Object.defineProperty(e,"GlobalStore",{enumerable:!0,get:function(){return l.GlobalStore}});var s=j(804);Object.defineProperty(e,"GlobalStoreAbstract",{enumerable:!0,get:function(){return s.GlobalStoreAbstract}});var i=j(670);Object.defineProperty(e,"createGlobalState",{enumerable:!0,get:function(){return i.createGlobalState}});var c=j(959);Object.defineProperty(e,"getLocalStorageItem",{enumerable:!0,get:function(){return c.getLocalStorageItem}});var u=j(683);Object.defineProperty(e,"setLocalStorageItem",{enumerable:!0,get:function(){return u.setLocalStorageItem}})})(),g})(),"object"==typeof exports&&"object"==typeof module?module.exports=t(require("./uniqueId.js"),require("./throwWrongKeyOnActionCollectionConfig.js"),require("./isRecord.js"),require("./createContext.js"),require("./createGlobalState.js"),require("./shallowCompare.js"),require("./setLocalStorageItem.js"),require("./GlobalStore.js"),require("./GlobalStoreAbstract.js"),require("./getLocalStorageItem.js")):"function"==typeof define&&define.amd?define(["./uniqueId.js","./throwWrongKeyOnActionCollectionConfig.js","./isRecord.js","./createContext.js","./createGlobalState.js","./shallowCompare.js","./setLocalStorageItem.js","./GlobalStore.js","./GlobalStoreAbstract.js","./getLocalStorageItem.js"],t):"object"==typeof exports?exports["react-global-state-hooks"]=t(require("./uniqueId.js"),require("./throwWrongKeyOnActionCollectionConfig.js"),require("./isRecord.js"),require("./createContext.js"),require("./createGlobalState.js"),require("./shallowCompare.js"),require("./setLocalStorageItem.js"),require("./GlobalStore.js"),require("./GlobalStoreAbstract.js"),require("./getLocalStorageItem.js")):e["react-global-state-hooks"]=t(e["./uniqueId.js"],e["./throwWrongKeyOnActionCollectionConfig.js"],e["./isRecord.js"],e["./createContext.js"],e["./createGlobalState.js"],e["./shallowCompare.js"],e["./setLocalStorageItem.js"],e["./GlobalStore.js"],e["./GlobalStoreAbstract.js"],e["./getLocalStorageItem.js"]);
1
+ var e,r;e=this,r=(e,r,t,o,n,a,l,i)=>(()=>{"use strict";var s={78:r=>{r.exports=e},361:e=>{e.exports=r},487:e=>{e.exports=t},623:e=>{e.exports=o},670:e=>{e.exports=n},673:e=>{e.exports=a},778:e=>{e.exports=l},804:e=>{e.exports=i}},c={};function u(e){var r=c[e];if(void 0!==r)return r.exports;var t=c[e]={exports:{}};return s[e](t,t.exports,u),t.exports}var b={};return(()=>{var e=b;Object.defineProperty(e,"__esModule",{value:!0}),e.createGlobalState=e.GlobalStoreAbstract=e.GlobalStore=e.createContext=e.isRecord=e.throwWrongKeyOnActionCollectionConfig=e.uniqueId=e.shallowCompare=void 0;var r=u(673);Object.defineProperty(e,"shallowCompare",{enumerable:!0,get:function(){return r.shallowCompare}});var t=u(78);Object.defineProperty(e,"uniqueId",{enumerable:!0,get:function(){return t.uniqueId}});var o=u(361);Object.defineProperty(e,"throwWrongKeyOnActionCollectionConfig",{enumerable:!0,get:function(){return o.throwWrongKeyOnActionCollectionConfig}});var n=u(487);Object.defineProperty(e,"isRecord",{enumerable:!0,get:function(){return n.isRecord}});var a=u(623);Object.defineProperty(e,"createContext",{enumerable:!0,get:function(){return a.createContext}});var l=u(778);Object.defineProperty(e,"GlobalStore",{enumerable:!0,get:function(){return l.GlobalStore}});var i=u(804);Object.defineProperty(e,"GlobalStoreAbstract",{enumerable:!0,get:function(){return i.GlobalStoreAbstract}});var s=u(670);Object.defineProperty(e,"createGlobalState",{enumerable:!0,get:function(){return s.createGlobalState}})})(),b})(),"object"==typeof exports&&"object"==typeof module?module.exports=r(require("./uniqueId.js"),require("./throwWrongKeyOnActionCollectionConfig.js"),require("./isRecord.js"),require("./createContext.js"),require("./createGlobalState.js"),require("./shallowCompare.js"),require("./GlobalStore.js"),require("./GlobalStoreAbstract.js")):"function"==typeof define&&define.amd?define(["./uniqueId.js","./throwWrongKeyOnActionCollectionConfig.js","./isRecord.js","./createContext.js","./createGlobalState.js","./shallowCompare.js","./GlobalStore.js","./GlobalStoreAbstract.js"],r):"object"==typeof exports?exports["react-global-state-hooks"]=r(require("./uniqueId.js"),require("./throwWrongKeyOnActionCollectionConfig.js"),require("./isRecord.js"),require("./createContext.js"),require("./createGlobalState.js"),require("./shallowCompare.js"),require("./GlobalStore.js"),require("./GlobalStoreAbstract.js")):e["react-global-state-hooks"]=r(e["./uniqueId.js"],e["./throwWrongKeyOnActionCollectionConfig.js"],e["./isRecord.js"],e["./createContext.js"],e["./createGlobalState.js"],e["./shallowCompare.js"],e["./GlobalStore.js"],e["./GlobalStoreAbstract.js"]);
@@ -1,7 +1,7 @@
1
1
  import type React from 'react';
2
2
  import { StateHook, BaseMetadata, ActionCollectionConfig, ActionCollectionResult, GlobalStoreCallbacks } from 'react-hooks-global-states/types';
3
3
  export type { InferActionsType } from 'react-hooks-global-states/createGlobalState';
4
- import { LocalStorageConfig } from './types';
4
+ import type { LocalStorageConfig } from './types';
5
5
  interface CreateGlobalState {
6
6
  /**
7
7
  * Creates a global state hook.
@@ -78,7 +78,7 @@ interface CreateGlobalState {
78
78
  metadata?: Metadata;
79
79
  callbacks?: GlobalStoreCallbacks<State, PublicStateMutator, Metadata>;
80
80
  actions?: ActionsConfig;
81
- localStorage?: LocalStorageConfig;
81
+ localStorage?: LocalStorageConfig<State>;
82
82
  }): StateHook<State, PublicStateMutator, Metadata>;
83
83
  /**
84
84
  * Creates a global state hook that you can use across your application
@@ -135,7 +135,7 @@ interface CreateGlobalState {
135
135
  metadata?: Metadata;
136
136
  callbacks?: GlobalStoreCallbacks<State, PublicStateMutator, Metadata>;
137
137
  actions: ActionsConfig;
138
- localStorage?: LocalStorageConfig;
138
+ localStorage?: LocalStorageConfig<State>;
139
139
  }): StateHook<State, PublicStateMutator, Metadata>;
140
140
  }
141
141
  /**
package/index.d.ts CHANGED
@@ -12,5 +12,3 @@ export { type LocalStorageConfig } from './types';
12
12
  export { GlobalStore } from './GlobalStore';
13
13
  export { GlobalStoreAbstract } from './GlobalStoreAbstract';
14
14
  export { createGlobalState, type InferActionsType } from './createGlobalState';
15
- export { getLocalStorageItem } from './getLocalStorageItem';
16
- export { setLocalStorageItem } from './setLocalStorageItem';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "react-global-state-hooks",
3
- "version": "14.0.2",
4
- "description": "This is a package to easily handling global-state across your react components No-redux, No-context.",
3
+ "version": "14.1.0-beta.1",
4
+ "description": "This is a package to easily handling global-state across your react components",
5
5
  "main": "./bundle.js",
6
6
  "types": "./index.d.ts",
7
7
  "sideEffects": false,
@@ -36,16 +36,6 @@
36
36
  "require": "./types.js",
37
37
  "types": "./types.d.ts"
38
38
  },
39
- "./getLocalStorageItem": {
40
- "import": "./getLocalStorageItem.js",
41
- "require": "./getLocalStorageItem.js",
42
- "types": "./getLocalStorageItem.d.ts"
43
- },
44
- "./setLocalStorageItem": {
45
- "import": "./setLocalStorageItem.js",
46
- "require": "./setLocalStorageItem.js",
47
- "types": "./setLocalStorageItem.d.ts"
48
- },
49
39
  "./isRecord": {
50
40
  "import": "./isRecord.js",
51
41
  "require": "./isRecord.js",
@@ -72,16 +62,20 @@
72
62
  "*.d.ts"
73
63
  ],
74
64
  "scripts": {
65
+ "format": "prettier --write . --log-level silent && yarn lint:fix",
66
+ "test": "yarn build && yarn jest",
67
+ "test:debug:no-watch": "node --inspect-brk node_modules/.bin/jest --no-watchman --runInBand",
75
68
  "test:debug": "node --inspect-brk node_modules/.bin/jest --watch --runInBand",
76
- "test:quick": "jest --maxWorkers=4 -c --no-watchman -u",
77
- "test:coverage": "jest --maxWorkers=4 -c --colors --no-watchman --verbose --coverage",
78
- "build": "yarn ts-check &&yarn clean && webpack --config webpack.config.js",
79
- "prepare": "yarn ts-check && npm run build",
80
- "version": "npm run format && git add -A src",
69
+ "test:quick": "yarn test --no-coverage --maxWorkers=4 -c --no-watchman -u",
70
+ "test:coverage": "yarn test --maxWorkers=4 -c --colors --no-watchman --verbose --coverage",
71
+ "build": "yarn ts-check && yarn clean && webpack --config webpack.config.js",
72
+ "prepare": "yarn format && npm run build && yarn test:quick",
73
+ "version": "npm run format && yarn build && yarn jest && git add -A",
81
74
  "postversion": "git push && git push --tags",
82
- "clean": "find . -maxdepth 1 -type f \\( -name '*.js' -o -name '*.d.ts' \\) ! -name 'webpack.config.js' -exec rm {} +",
83
- "format": "prettier --write .",
84
- "ts-check": "tsc -p tsconfig.json --pretty true --noEmit true"
75
+ "lint": "eslint . --max-warnings=0",
76
+ "lint:fix": "eslint . --fix --max-warnings=0",
77
+ "clean": "find . -maxdepth 1 -type f \\( -name '*.js' -o -name '*.d.ts' \\) ! -name 'webpack.config.js' ! -name 'eslint.config.mts' -exec rm {} + && rm -rf coverage",
78
+ "ts-check": "tsc -p tsconfig.json --noEmit"
85
79
  },
86
80
  "repository": {
87
81
  "type": "git",
@@ -122,18 +116,27 @@
122
116
  "@babel/preset-env": "^7.20.2",
123
117
  "@babel/preset-react": "^7.18.6",
124
118
  "@babel/preset-typescript": "^7.21.0",
119
+ "@eslint/js": "^9.39.1",
125
120
  "@testing-library/dom": "^10.4.0",
126
121
  "@testing-library/react": "^16.3.0",
127
122
  "@types/jest": "^29.5.11",
128
123
  "@types/minimatch": "^6.0.0",
129
124
  "@types/react": "^18.2.45",
130
125
  "@types/react-dom": "^18.2.18",
126
+ "@typescript-eslint/eslint-plugin": "^4.9.1",
127
+ "@typescript-eslint/parser": "^4.9.1",
131
128
  "babel-loader": "^9.1.2",
132
129
  "clean-webpack-plugin": "^4.0.0",
133
130
  "easy-cancelable-promise": "^1.0.1",
131
+ "eslint": "^9.39.1",
132
+ "eslint-config-airbnb": "^18.2.1",
133
+ "eslint-plugin-import": "^2.22.1",
134
+ "eslint-plugin-jsx-a11y": "^6.4.1",
135
+ "eslint-plugin-react": "^7.37.5",
136
+ "eslint-plugin-react-hooks": "^4.2.0",
134
137
  "jest": "^29.7.0",
135
138
  "jest-environment-jsdom": "^30.0.4",
136
- "json-storage-formatter": "^2.0.9",
139
+ "jiti": "^2.6.1",
137
140
  "prettier": "^3.6.2",
138
141
  "react": "^18.2.0",
139
142
  "react-dom": "^18.2.0",
@@ -141,14 +144,24 @@
141
144
  "ts-loader": "^9.5.1",
142
145
  "tslib": "^2.6.2",
143
146
  "typescript": "^5.8.2",
147
+ "typescript-eslint": "^8.46.3",
144
148
  "webpack": "^5.76.3",
145
149
  "webpack-cli": "^5.0.1"
146
150
  },
151
+ "dependencies": {
152
+ "react-hooks-global-states": "14.1.0-beta.2",
153
+ "json-storage-formatter": "^3.0.2"
154
+ },
147
155
  "peerDependencies": {
148
- "json-storage-formatter": "^2.0.9",
149
- "react": ">=18.0.0"
156
+ "react": ">=18.0.0",
157
+ "json-storage-formatter": "^3.0.2"
150
158
  },
151
- "dependencies": {
152
- "react-hooks-global-states": "^14.0.2"
159
+ "peerDependenciesMeta": {
160
+ "react": {
161
+ "optional": false
162
+ },
163
+ "json-storage-formatter": {
164
+ "optional": false
165
+ }
153
166
  }
154
167
  }
package/tryCatch.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ type TryCatchResult<T extends (...args: any[]) => any> = {
2
+ result: ReturnType<T> | null;
3
+ error: unknown;
4
+ };
5
+ /**
6
+ * Simple linear try catch utility to avoid repetitive try catch blocks
7
+ */
8
+ declare function tryCatch<T extends () => any>(callback: T): TryCatchResult<T>;
9
+ export default tryCatch;
package/types.d.ts CHANGED
@@ -1,12 +1,90 @@
1
1
  export type { StateApi, ObservableFragment, MetadataSetter, StateChanges, StoreTools, ActionCollectionResult, GlobalStoreCallbacks, UseHookOptions, UnsubscribeCallback, SubscribeCallbackConfig, SubscribeCallback, BaseMetadata, MetadataGetter, SelectorCallback, SubscriberParameters, SubscriptionCallback, StateHook, ActionCollectionConfig, } from 'react-hooks-global-states/types';
2
- export type LocalStorageConfig = {
3
- key: string | (() => string);
2
+ /**
3
+ * @description Configuration for persisting state in localStorage
4
+ */
5
+ export type LocalStorageConfig<State> = {
4
6
  /**
5
- * The function used to encrypt the local storage, it can be a custom function or a boolean value (true = atob)
7
+ * @description The key used to store the item in localStorage.
6
8
  */
7
- encrypt?: boolean | ((value: string) => string);
9
+ key: string;
8
10
  /**
9
- * The function used to decrypt the local storage, it can be a custom function or a boolean value (true = atob)
11
+ * @description Validator function to ensure the integrity of the restored state.
12
+ * Receives the restored value and the initial state, must return a valid `State`.
13
+ * Executes after every initialization from localStorage, including after migration.
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * validator: ({ restored, initial }) => {
18
+ * if (typeof restored !== 'number') {
19
+ * return initial;
20
+ * }
21
+ *
22
+ * return restored;
23
+ * }
24
+ * ```
10
25
  */
11
- decrypt?: boolean | ((value: string) => string);
26
+ validator: (args: {
27
+ restored: unknown;
28
+ initial: State;
29
+ }) => State;
30
+ /**
31
+ * @description Error callback invoked when an exception occurs during any persistence phase.
32
+ * Use this to log or report issues without throwing.
33
+ */
34
+ onError?: (error: unknown) => void;
35
+ versioning?: {
36
+ /**
37
+ * @description Current schema version for this item. When the stored version differs,
38
+ * the `migrator` function is invoked to upgrade the stored value.
39
+ * @default -1
40
+ * @example
41
+ * ```ts
42
+ * {
43
+ * key: 'counter',
44
+ * version: 1
45
+ * }
46
+ * ```
47
+ */
48
+ version: string | number;
49
+ /**
50
+ * @description Called when a stored value is found with a different version.
51
+ * Receives the raw stored value and must return the upgraded `State`.
52
+ * If and error is thrown during migration, the `onError` callback is invoked
53
+ * and the state falls back to the initial value.
54
+ */
55
+ migrator: (args: {
56
+ legacy: unknown;
57
+ initial: State;
58
+ }) => State;
59
+ };
60
+ /**
61
+ * @description High level overrides of the localstorage synchronization
62
+ * Use it if you want to have full control of how the state is stored/retrieved.
63
+ *
64
+ * This disables versioning, migration
65
+ */
66
+ adapter?: {
67
+ /**
68
+ * @description Custom setter for the stored value associated with `key`.
69
+ */
70
+ setItem: (key: string, value: State) => void;
71
+ /**
72
+ * @description Custom getter for the stored value associated with `key`.
73
+ * Should return the previously stored value (parsed/decoded to `State`) for that key.
74
+ */
75
+ getItem: (key: string) => State;
76
+ };
77
+ };
78
+ /**
79
+ * @description Structure of the item stored in localStorage
80
+ */
81
+ export type ItemEnvelope<T> = {
82
+ /**
83
+ * @description Actual stored state
84
+ */
85
+ s: T;
86
+ /**
87
+ * @description Version of the stored state
88
+ */
89
+ v?: string | number;
12
90
  };
package/webpack.config.js CHANGED
@@ -7,15 +7,11 @@ const individualEntries = {
7
7
  GlobalStore: './src/GlobalStore.ts',
8
8
  GlobalStoreAbstract: './src/GlobalStoreAbstract.ts',
9
9
  createGlobalState: './src/createGlobalState.ts',
10
- types: './src/types.ts',
11
10
  isRecord: './src/isRecord.ts',
12
11
  shallowCompare: './src/shallowCompare.ts',
13
12
  throwWrongKeyOnActionCollectionConfig: './src/throwWrongKeyOnActionCollectionConfig.ts',
13
+ types: './src/types.ts',
14
14
  uniqueId: './src/uniqueId.ts',
15
-
16
- // extras
17
- getLocalStorageItem: './src/getLocalStorageItem.ts',
18
- setLocalStorageItem: './src/setLocalStorageItem.ts',
19
15
  };
20
16
 
21
17
  const getExternalsForEntries = () => {
@@ -1,3 +0,0 @@
1
- import { LocalStorageConfig } from './types';
2
- export declare const getLocalStorageItem: <T>(args: LocalStorageConfig) => T | null;
3
- export default getLocalStorageItem;
@@ -1 +0,0 @@
1
- var t;t=t=>(()=>{"use strict";var e={330:e=>{e.exports=t},527:(t,e,r)=>{Object.defineProperty(e,"__esModule",{value:!0}),e.getLocalStorageItem=void 0;var o=r(330);e.getLocalStorageItem=function(t){var e=t.key;if(!e)return null;var r="function"==typeof e?e():e,a=localStorage.getItem(r);if(null===a)return null;var n=t.decrypt||t.encrypt?"function"==typeof t.decrypt?t.decrypt(a):atob(a):a;return(0,o.formatFromStore)(n,{jsonParse:!0})},e.default=e.getLocalStorageItem}},r={};return function t(o){var a=r[o];if(void 0!==a)return a.exports;var n=r[o]={exports:{}};return e[o](n,n.exports,t),n.exports}(527)})(),"object"==typeof exports&&"object"==typeof module?module.exports=t(require("json-storage-formatter/formatFromStore")):"function"==typeof define&&define.amd?define(["json-storage-formatter/formatFromStore"],t):"object"==typeof exports?exports["react-global-state-hooks"]=t(require("json-storage-formatter/formatFromStore")):this["react-global-state-hooks"]=t(this["json-storage-formatter/formatFromStore"]);
@@ -1,3 +0,0 @@
1
- import { LocalStorageConfig } from './types';
2
- export declare const setLocalStorageItem: <T>(item: T, args: LocalStorageConfig) => void;
3
- export default setLocalStorageItem;
@@ -1 +0,0 @@
1
- var t;t=t=>(()=>{"use strict";var e={339:(t,e,o)=>{Object.defineProperty(e,"__esModule",{value:!0}),e.setLocalStorageItem=void 0;var r=o(413);e.setLocalStorageItem=function(t,e){var o=e.key;if(o){var a="function"==typeof o?o():o,s=(0,r.formatToStore)(t,{stringify:!0,excludeTypes:["function"]}),f=e.encrypt?"function"==typeof e.encrypt?e.encrypt(s):btoa(s):s;localStorage.setItem(a,f)}},e.default=e.setLocalStorageItem},413:e=>{e.exports=t}},o={};return function t(r){var a=o[r];if(void 0!==a)return a.exports;var s=o[r]={exports:{}};return e[r](s,s.exports,t),s.exports}(339)})(),"object"==typeof exports&&"object"==typeof module?module.exports=t(require("json-storage-formatter/formatToStore")):"function"==typeof define&&define.amd?define(["json-storage-formatter/formatToStore"],t):"object"==typeof exports?exports["react-global-state-hooks"]=t(require("json-storage-formatter/formatToStore")):this["react-global-state-hooks"]=t(this["json-storage-formatter/formatToStore"]);