react-hooks-global-states 15.0.12 → 15.0.15
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.js +1 -1
- package/createContext.js +1 -1
- package/createGlobalState.d.ts +1 -0
- package/package.json +1 -1
- package/webpack.config.js +6 -1
- package/README.proposal.md +0 -1191
package/GlobalStore.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var t,e;t=this,e=(t,e,s,i,n,a)=>(()=>{"use strict";var o={78:e=>{e.exports=t},132:function(t,e,s){var i,n=this&&this.__createBinding||(Object.create?function(t,e,s,i){void 0===i&&(i=s);var n=Object.getOwnPropertyDescriptor(e,s);n&&!("get"in n?!e.__esModule:n.writable||n.configurable)||(n={enumerable:!0,get:function(){return e[s]}}),Object.defineProperty(t,i,n)}:function(t,e,s,i){void 0===i&&(i=s),t[i]=e[s]}),a=this&&this.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,"default",{enumerable:!0,value:e})}:function(t,e){t.default=e}),o=this&&this.__importStar||(i=function(t){return i=Object.getOwnPropertyNames||function(t){var e=[];for(var s in t)Object.prototype.hasOwnProperty.call(t,s)&&(e[e.length]=s);return e},i(t)},function(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var s=i(t),o=0;o<s.length;o++)"default"!==s[o]&&n(e,t,s[o]);return a(e,t),e}),r=this&&this.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(e,"__esModule",{value:!0}),e.GlobalStore=void 0,e.createObservable=v,e.createSelectorHook=g;const l=s(155),c=r(s(506)),u=r(s(487)),d=o(s(673)),h=r(s(361)),b=r(s(78)),p=r(s(627));class f{constructor(t,e={metadata:{}}){var s,i;this.cleanupFunctions=[],this.actionsConfig=null,this.callbacks=null,this.actions=null,this.subscribers=new Set,this.createSelectorHook=g,this.createObservable=v;const{metadata:n,callbacks:a,actions:o,name:r}=e;if((0,c.default)(t)&&(this.stateCallback=t),(0,c.default)(n)&&(this.metadataCallback=n),this._name=null!=r?r:(0,b.default)("gs:"),this.state=this.stateCallback?this.stateCallback():t,this.metadata=this.metadataCallback?this.metadataCallback():null!=n?n:{},this.callbacks=null!=a?a:null,this.actionsConfig=null!=o?o:null,p.default.isDevToolsPresent){const t=null!==(s=(new Error).stack)&&void 0!==s?s:"";null===(i=p.default.REACT_GLOBAL_STATE_HOOK_DEBUG)||void 0===i||i.call(p.default,this,e,t)}this.constructor!==f||this.initialize()}async initialize(){var t,e,s,i,n,a,o,r;const l=null!==(e=null===(t=this.__devtools_initialize_getStoreActionsMapWrapped)||void 0===t?void 0:t.call(this))&&void 0!==e?e:this.getStoreActionsMap(),{actions:u,storeTools:d}=l;this.actions=u,this.storeTools=d,this.use=this.getMainHook();const h=null!==(i=null===(s=this.onInit)||void 0===s?void 0:s.call(this))&&void 0!==i?i:null;(0,c.default)(h)&&this.cleanupFunctions.push(h);const{onInit:b}=null!==(n=this.callbacks)&&void 0!==n?n:{};if(!b)return;const p=null!==(o=null===(a=this.__devtools_getLifeCycleStoreToolsWrapper)||void 0===a?void 0:a.call(this,"config/onInit/"))&&void 0!==o?o:d,f=null!==(r=null==b?void 0:b(p))&&void 0!==r?r:null;(0,c.default)(f)&&this.cleanupFunctions.push(f)}executeSetStateForSubscriber(t,e){const{selector:s,onStoreChange:i,currentState:n,isEqualRoot:a=(t,e)=>t===e,isEqual:o=(t,e)=>t===e}=t;if(!e.forceUpdate&&a(e.currentState,e.newState))return{didUpdate:!1};const r=s?s(e.newState):e.newState;return!e.forceUpdate&&o(n,r)?{didUpdate:!1}:(this.partialUpdateSubscription(t,{currentState:r}),i({state:r},{identifier:e.identifier}),{didUpdate:!0})}setActualStateWithoutValidations(t,{forceUpdate:e,identifier:s}){const i=this.state;if(!e&&i===t)return;this.state=t;const n=this.subscribers.values(),a={forceUpdate:e,newState:t,currentState:i,identifier:s};for(const t of n)this.executeSetStateForSubscriber(t,a)}setMetadata(t){const e=(0,c.default)(t)?t(this.metadata):t;this.metadata=e}getMetadata(){return this.metadata}getState(){return this.state}subscribe(...[t,e,s]){var i;const n=(0,c.default)(e),a=n?t:void 0,o=n?e:t,r=null!==(i=n?s:e)&&void 0!==i?i:void 0,l=a?a(this.state):this.state;(null==r?void 0:r.skipFirst)||o(l);const u=Object.assign({selector:a,currentState:l,onStoreChange:({state:t})=>o(t)},r);return this.subscribeCallback(u),()=>{this.subscribers.delete(u)}}subscribeCallback(t){var e,s;return null===(s=null===(e=this.callbacks)||void 0===e?void 0:e.onSubscribed)||void 0===s||s.call(e,this.storeTools,t),this.subscribers.add(t),()=>{this.subscribers.delete(t)}}partialUpdateSubscription(t,e){Object.assign(t,e)}getMainHook(){const t=`globalHook:${this._name}`,e=(e,s=[])=>{(0,l.useDebugValue)(t);const i=(0,d.isArray)(s)?{dependencies:s}:null!=s?s:{},n=(0,l.useRef)(null),a=t=>(0,c.default)(e)?e(t):t;n.current=(()=>n.current?n.current:Object.assign({selector:a,currentState:a(this.state),onStoreChange:()=>{throw new Error("Callback not set")}},i))();const o=n.current.dependencies,r=null==i?void 0:i.dependencies,u=Object.assign(Object.assign({},i),{selector:a,dependencies:r});this.partialUpdateSubscription(n.current,u);const{subscribe:h,getSnapshot:b,getServerSnapshot:p}=(0,l.useMemo)((()=>{const t=()=>n.current.currentState;return{subscribe:t=>(n.current.onStoreChange=t,this.subscribeCallback(n.current)),getSnapshot:t,getServerSnapshot:t}}),[]);return this.reselectIfDependenciesChanged({subscription:n.current,newDependencies:r,currentDependencies:o}),[(0,l.useSyncExternalStore)(h,b,p),this.getStateOrchestrator(),this.metadata]},s={actions:this.actions,createObservable:this.createObservable.bind(this),createSelectorHook:this.createSelectorHook.bind(this),dispose:this.dispose.bind(this),getMetadata:this.getMetadata.bind(this),getState:this.getState.bind(this),reset:this.reset.bind(this),setMetadata:this.setMetadata.bind(this),setState:this.setState.bind(this),subscribe:this.subscribe.bind(this),subscribers:this.subscribers,use:e,select:(...t)=>e(...t)[0]};return Object.assign(e,s),e}reselectIfDependenciesChanged({subscription:t,newDependencies:e,currentDependencies:s}){t.selector&&s!==e&&((null==s?void 0:s.length)===(null==e?void 0:e.length)&&(0,d.default)(s,e)||this.partialUpdateSubscription(t,{currentState:t.selector(this.state)}))}getStateOrchestrator(){return(()=>this.actions?this.actions:this.setState.bind(this))()}setState(t,{forceUpdate:e,identifier:s}={}){var i,n,a,o,r,l;const u=this.state,d=(0,c.default)(t)?t(u):t;if(!e&&this.state===d)return;const h=this.setActualStateWithoutValidations,b={previousState:u,state:d,identifier:s},p=Object.assign(Object.assign(Object.assign({},this.storeTools),b),{setState:h});(null===(i=this.callbacks)||void 0===i?void 0:i.computePreventStateChange)&&(null===(a=null===(n=this.callbacks)||void 0===n?void 0:n.computePreventStateChange)||void 0===a?void 0:a.call(n,p))||(this.setActualStateWithoutValidations(d,{forceUpdate:e,identifier:s}),null===(o=this.onStateChanged)||void 0===o||o.call(this,p),null===(l=null===(r=this.callbacks)||void 0===r?void 0:r.onStateChanged)||void 0===l||l.call(r,p))}getStoreActionsMap(){const t={setMetadata:this.setMetadata.bind(this),getMetadata:this.getMetadata.bind(this),getState:this.getState.bind(this),setState:this.setState.bind(this),subscribe:this.subscribe.bind(this),actions:null};if(!(0,u.default)(this.actionsConfig))return{actions:null,storeTools:t};const e=this.actionsConfig,s={};t.actions=s;const i=Object.keys(e);for(const n of i)Object.assign(s,{[n](...i){const a=e[n].apply(s,i);return"function"!=typeof a&&(0,h.default)(n),a.call(s,t)}});return{actions:s,storeTools:t}}removeSubscriptions(){this.subscribers.clear()}executeCleanupTasks(){this.cleanupFunctions.forEach((t=>{null==t||t()})),this.cleanupFunctions.length=0}dispose(){this.removeSubscriptions(),this.executeCleanupTasks()}reset(t,s){var i,n,a,o,r,l;this.executeCleanupTasks();const u=(()=>t===e.DEFAULT?this.state:this.stateCallback?this.stateCallback():t)(),d=(()=>s===e.DEFAULT?this.metadata:this.metadataCallback?this.metadataCallback():s)();this.setActualStateWithoutValidations(u,{forceUpdate:!0}),this.metadata=d;const h=null!==(n=null===(i=this.onInit)||void 0===i?void 0:i.call(this))&&void 0!==n?n:null;(0,c.default)(h)&&this.cleanupFunctions.push(h);const{onInit:b}=null!==(a=this.callbacks)&&void 0!==a?a:{};if(!b)return;const p=null!==(r=null===(o=this.__devtools_getLifeCycleStoreToolsWrapper)||void 0===o?void 0:o.call(this,"config/onInit/"))&&void 0!==r?r:this.storeTools,f=null!==(l=null==b?void 0:b(p))&&void 0!==l?l:null;(0,c.default)(f)&&this.cleanupFunctions.push(f)}}function v(t,e){const s=null==e?void 0:e.name,i=null==e?void 0:e.isEqualRoot,n=null==e?void 0:e.isEqual;let a=this.getState(),o=(null!=t?t:t=>t)(a);const r=new f(o,{name:null!=s?s:(0,b.default)("sh:")}),l=this.subscribe((e=>{if((null!=i?i:Object.is)(a,e))return;a=e;const s=t(e);(null!=n?n:Object.is)(o,s)||(o=s,r.setState(s))}),{skipFirst:!0}),c=r.subscribe.bind(r),u={getState:r.getState.bind(r),subscribe:r.subscribe.bind(r),createSelectorHook:g.bind(c),createObservable:v.bind(c),dispose:()=>{l(),r.dispose()},subscribers:r.subscribers};return Object.assign(c,u),c}function g(t,e){const s=null==e?void 0:e.name,i=null==e?void 0:e.isEqualRoot,n=null==e?void 0:e.isEqual;let a=this.getState(),o=(null!=t?t:t=>t)(a);const r=new f(o,{name:null!=s?s:(0,b.default)("sh:")}),l=this.subscribe((e=>{if((null!=i?i:Object.is)(a,e))return;a=e;const s=t(e);(null!=n?n:Object.is)(o,s)||(o=s,r.setState(s))}),{skipFirst:!0}),c=(...t)=>{const[e]=r.use(...t);return e},u={getState:()=>r.getState(),subscribe:r.subscribe.bind(r),createSelectorHook:g.bind(c),createObservable:v.bind(c),dispose:()=>{l(),r.dispose()},subscribers:r.subscribers};return Object.assign(c,u),c}e.GlobalStore=f,e.default=f},155:t=>{t.exports=e},361:t=>{t.exports=s},487:t=>{t.exports=i},506:t=>{t.exports=n},627:(t,e)=>{Object.defineProperty(e,"__esModule",{value:!0});const s=globalThis;s.isDevToolsPresent=Boolean(s.REACT_GLOBAL_STATE_HOOK_DEBUG),e.default=s},673:t=>{t.exports=a}},r={};return function t(e){var s=r[e];if(void 0!==s)return s.exports;var i=r[e]={exports:{}};return o[e].call(i.exports,i,i.exports,t),i.exports}(132)})(),"object"==typeof exports&&"object"==typeof module?module.exports=e(require("./uniqueId.js"),require("react"),require("./throwWrongKeyOnActionCollectionConfig.js"),require("./isRecord.js"),require("json-storage-formatter/isFunction"),require("./shallowCompare.js")):"function"==typeof define&&define.amd?define(["./uniqueId.js","react","./throwWrongKeyOnActionCollectionConfig.js","./isRecord.js","json-storage-formatter/isFunction","./shallowCompare.js"],e):"object"==typeof exports?exports["react-hooks-global-states"]=e(require("./uniqueId.js"),require("react"),require("./throwWrongKeyOnActionCollectionConfig.js"),require("./isRecord.js"),require("json-storage-formatter/isFunction"),require("./shallowCompare.js")):t["react-hooks-global-states"]=e(t["./uniqueId.js"],t.react,t["./throwWrongKeyOnActionCollectionConfig.js"],t["./isRecord.js"],t["json-storage-formatter/isFunction"],t["./shallowCompare.js"]);
|
|
1
|
+
var t,e;t=this,e=(t,e,s,i,n,a)=>(()=>{"use strict";var o={12:e=>{e.exports=t},78:t=>{t.exports=e},132:function(t,e,s){var i,n=this&&this.__createBinding||(Object.create?function(t,e,s,i){void 0===i&&(i=s);var n=Object.getOwnPropertyDescriptor(e,s);n&&!("get"in n?!e.__esModule:n.writable||n.configurable)||(n={enumerable:!0,get:function(){return e[s]}}),Object.defineProperty(t,i,n)}:function(t,e,s,i){void 0===i&&(i=s),t[i]=e[s]}),a=this&&this.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,"default",{enumerable:!0,value:e})}:function(t,e){t.default=e}),o=this&&this.__importStar||(i=function(t){return i=Object.getOwnPropertyNames||function(t){var e=[];for(var s in t)Object.prototype.hasOwnProperty.call(t,s)&&(e[e.length]=s);return e},i(t)},function(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var s=i(t),o=0;o<s.length;o++)"default"!==s[o]&&n(e,t,s[o]);return a(e,t),e}),r=this&&this.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(e,"__esModule",{value:!0}),e.GlobalStore=void 0,e.createObservable=v,e.createSelectorHook=g;const l=s(12),c=r(s(506)),u=r(s(487)),d=o(s(673)),h=r(s(361)),b=r(s(78)),p=r(s(627));class f{constructor(t,e={metadata:{}}){var s,i;this.cleanupFunctions=[],this.actionsConfig=null,this.callbacks=null,this.actions=null,this.subscribers=new Set,this.createSelectorHook=g,this.createObservable=v;const{metadata:n,callbacks:a,actions:o,name:r}=e;if((0,c.default)(t)&&(this.stateCallback=t),(0,c.default)(n)&&(this.metadataCallback=n),this._name=null!=r?r:(0,b.default)("gs:"),this.state=this.stateCallback?this.stateCallback():t,this.metadata=this.metadataCallback?this.metadataCallback():null!=n?n:{},this.callbacks=null!=a?a:null,this.actionsConfig=null!=o?o:null,p.default.isDevToolsPresent){const t=null!==(s=(new Error).stack)&&void 0!==s?s:"";null===(i=p.default.REACT_GLOBAL_STATE_HOOK_DEBUG)||void 0===i||i.call(p.default,this,e,t)}this.constructor!==f||this.initialize()}async initialize(){var t,e,s,i,n,a,o,r;const l=null!==(e=null===(t=this.__devtools_initialize_getStoreActionsMapWrapped)||void 0===t?void 0:t.call(this))&&void 0!==e?e:this.getStoreActionsMap(),{actions:u,storeTools:d}=l;this.actions=u,this.storeTools=d,this.use=this.getMainHook();const h=null!==(i=null===(s=this.onInit)||void 0===s?void 0:s.call(this))&&void 0!==i?i:null;(0,c.default)(h)&&this.cleanupFunctions.push(h);const{onInit:b}=null!==(n=this.callbacks)&&void 0!==n?n:{};if(!b)return;const p=null!==(o=null===(a=this.__devtools_getLifeCycleStoreToolsWrapper)||void 0===a?void 0:a.call(this,"config/onInit/"))&&void 0!==o?o:d,f=null!==(r=null==b?void 0:b(p))&&void 0!==r?r:null;(0,c.default)(f)&&this.cleanupFunctions.push(f)}executeSetStateForSubscriber(t,e){const{selector:s,onStoreChange:i,currentState:n,isEqualRoot:a=(t,e)=>t===e,isEqual:o=(t,e)=>t===e}=t;if(!e.forceUpdate&&a(e.currentState,e.newState))return{didUpdate:!1};const r=s?s(e.newState):e.newState;return!e.forceUpdate&&o(n,r)?{didUpdate:!1}:(this.partialUpdateSubscription(t,{currentState:r}),i({state:r},{identifier:e.identifier}),{didUpdate:!0})}setActualStateWithoutValidations(t,{forceUpdate:e,identifier:s}){const i=this.state;if(!e&&i===t)return;this.state=t;const n=this.subscribers.values(),a={forceUpdate:e,newState:t,currentState:i,identifier:s};for(const t of n)this.executeSetStateForSubscriber(t,a)}setMetadata(t){const e=(0,c.default)(t)?t(this.metadata):t;this.metadata=e}getMetadata(){return this.metadata}getState(){return this.state}subscribe(...[t,e,s]){var i;const n=(0,c.default)(e),a=n?t:void 0,o=n?e:t,r=null!==(i=n?s:e)&&void 0!==i?i:void 0,l=a?a(this.state):this.state;(null==r?void 0:r.skipFirst)||o(l);const u=Object.assign({selector:a,currentState:l,onStoreChange:({state:t})=>o(t)},r);return this.subscribeCallback(u),()=>{this.subscribers.delete(u)}}subscribeCallback(t){var e,s;return null===(s=null===(e=this.callbacks)||void 0===e?void 0:e.onSubscribed)||void 0===s||s.call(e,this.storeTools,t),this.subscribers.add(t),()=>{this.subscribers.delete(t)}}partialUpdateSubscription(t,e){Object.assign(t,e)}getMainHook(){const t=`globalHook:${this._name}`,e=(e,s=[])=>{(0,l.useDebugValue)(t);const i=(0,d.isArray)(s)?{dependencies:s}:null!=s?s:{},n=(0,l.useRef)(null),a=t=>(0,c.default)(e)?e(t):t;n.current=(()=>n.current?n.current:Object.assign({selector:a,currentState:a(this.state),onStoreChange:()=>{throw new Error("Callback not set")}},i))();const o=n.current.dependencies,r=null==i?void 0:i.dependencies,u=Object.assign(Object.assign({},i),{selector:a,dependencies:r});this.partialUpdateSubscription(n.current,u);const{subscribe:h,getSnapshot:b,getServerSnapshot:p}=(0,l.useMemo)((()=>{const t=()=>n.current.currentState;return{subscribe:t=>(n.current.onStoreChange=t,this.subscribeCallback(n.current)),getSnapshot:t,getServerSnapshot:t}}),[]);return this.reselectIfDependenciesChanged({subscription:n.current,newDependencies:r,currentDependencies:o}),[(0,l.useSyncExternalStore)(h,b,p),this.getStateOrchestrator(),this.metadata]},s={actions:this.actions,createObservable:this.createObservable.bind(this),createSelectorHook:this.createSelectorHook.bind(this),dispose:this.dispose.bind(this),getMetadata:this.getMetadata.bind(this),getState:this.getState.bind(this),reset:this.reset.bind(this),setMetadata:this.setMetadata.bind(this),setState:this.setState.bind(this),subscribe:this.subscribe.bind(this),subscribers:this.subscribers,use:e,select:(...t)=>e(...t)[0]};return Object.assign(e,s),e}reselectIfDependenciesChanged({subscription:t,newDependencies:e,currentDependencies:s}){t.selector&&s!==e&&((null==s?void 0:s.length)===(null==e?void 0:e.length)&&(0,d.default)(s,e)||this.partialUpdateSubscription(t,{currentState:t.selector(this.state)}))}getStateOrchestrator(){return(()=>this.actions?this.actions:this.setState.bind(this))()}setState(t,{forceUpdate:e,identifier:s}={}){var i,n,a,o,r,l;const u=this.state,d=(0,c.default)(t)?t(u):t;if(!e&&this.state===d)return;const h=this.setActualStateWithoutValidations,b={previousState:u,state:d,identifier:s},p=Object.assign(Object.assign(Object.assign({},this.storeTools),b),{setState:h});(null===(i=this.callbacks)||void 0===i?void 0:i.computePreventStateChange)&&(null===(a=null===(n=this.callbacks)||void 0===n?void 0:n.computePreventStateChange)||void 0===a?void 0:a.call(n,p))||(this.setActualStateWithoutValidations(d,{forceUpdate:e,identifier:s}),null===(o=this.onStateChanged)||void 0===o||o.call(this,p),null===(l=null===(r=this.callbacks)||void 0===r?void 0:r.onStateChanged)||void 0===l||l.call(r,p))}getStoreActionsMap(){const t={setMetadata:this.setMetadata.bind(this),getMetadata:this.getMetadata.bind(this),getState:this.getState.bind(this),setState:this.setState.bind(this),subscribe:this.subscribe.bind(this),actions:null};if(!(0,u.default)(this.actionsConfig))return{actions:null,storeTools:t};const e=this.actionsConfig,s={};t.actions=s;const i=Object.keys(e);for(const n of i)Object.assign(s,{[n](...i){const a=e[n].apply(s,i);return"function"!=typeof a&&(0,h.default)(n),a.call(s,t)}});return{actions:s,storeTools:t}}removeSubscriptions(){this.subscribers.clear()}executeCleanupTasks(){this.cleanupFunctions.forEach((t=>{null==t||t()})),this.cleanupFunctions.length=0}dispose(){this.removeSubscriptions(),this.executeCleanupTasks()}reset(t,s){var i,n,a,o,r,l;this.executeCleanupTasks();const u=(()=>t===e.DEFAULT?this.state:this.stateCallback?this.stateCallback():t)(),d=(()=>s===e.DEFAULT?this.metadata:this.metadataCallback?this.metadataCallback():s)();this.setActualStateWithoutValidations(u,{forceUpdate:!0}),this.metadata=d;const h=null!==(n=null===(i=this.onInit)||void 0===i?void 0:i.call(this))&&void 0!==n?n:null;(0,c.default)(h)&&this.cleanupFunctions.push(h);const{onInit:b}=null!==(a=this.callbacks)&&void 0!==a?a:{};if(!b)return;const p=null!==(r=null===(o=this.__devtools_getLifeCycleStoreToolsWrapper)||void 0===o?void 0:o.call(this,"config/onInit/"))&&void 0!==r?r:this.storeTools,f=null!==(l=null==b?void 0:b(p))&&void 0!==l?l:null;(0,c.default)(f)&&this.cleanupFunctions.push(f)}}function v(t,e){const s=null==e?void 0:e.name,i=null==e?void 0:e.isEqualRoot,n=null==e?void 0:e.isEqual;let a=this.getState(),o=(null!=t?t:t=>t)(a);const r=new f(o,{name:null!=s?s:(0,b.default)("sh:")}),l=this.subscribe((e=>{if((null!=i?i:Object.is)(a,e))return;a=e;const s=t(e);(null!=n?n:Object.is)(o,s)||(o=s,r.setState(s))}),{skipFirst:!0}),c=r.subscribe.bind(r),u={getState:r.getState.bind(r),subscribe:r.subscribe.bind(r),createSelectorHook:g.bind(c),createObservable:v.bind(c),dispose:()=>{l(),r.dispose()},subscribers:r.subscribers};return Object.assign(c,u),c}function g(t,e){const s=null==e?void 0:e.name,i=null==e?void 0:e.isEqualRoot,n=null==e?void 0:e.isEqual;let a=this.getState(),o=(null!=t?t:t=>t)(a);const r=new f(o,{name:null!=s?s:(0,b.default)("sh:")}),l=this.subscribe((e=>{if((null!=i?i:Object.is)(a,e))return;a=e;const s=t(e);(null!=n?n:Object.is)(o,s)||(o=s,r.setState(s))}),{skipFirst:!0}),c=(...t)=>{const[e]=r.use(...t);return e},u={getState:()=>r.getState(),subscribe:r.subscribe.bind(r),createSelectorHook:g.bind(c),createObservable:v.bind(c),dispose:()=>{l(),r.dispose()},subscribers:r.subscribers};return Object.assign(c,u),c}e.GlobalStore=f,e.default=f},361:t=>{t.exports=s},487:t=>{t.exports=i},506:t=>{t.exports=n},627:(t,e)=>{Object.defineProperty(e,"__esModule",{value:!0});const s=globalThis;s.isDevToolsPresent=Boolean(s.REACT_GLOBAL_STATE_HOOK_DEBUG),e.default=s},673:t=>{t.exports=a}},r={};return function t(e){var s=r[e];if(void 0!==s)return s.exports;var i=r[e]={exports:{}};return o[e].call(i.exports,i,i.exports,t),i.exports}(132)})(),"object"==typeof exports&&"object"==typeof module?module.exports=e(require("react"),require("./uniqueId.js"),require("./throwWrongKeyOnActionCollectionConfig.js"),require("./isRecord.js"),require("json-storage-formatter/isFunction"),require("./shallowCompare.js")):"function"==typeof define&&define.amd?define(["react","./uniqueId.js","./throwWrongKeyOnActionCollectionConfig.js","./isRecord.js","json-storage-formatter/isFunction","./shallowCompare.js"],e):"object"==typeof exports?exports["react-hooks-global-states"]=e(require("react"),require("./uniqueId.js"),require("./throwWrongKeyOnActionCollectionConfig.js"),require("./isRecord.js"),require("json-storage-formatter/isFunction"),require("./shallowCompare.js")):t["react-hooks-global-states"]=e(t.React,t["./uniqueId.js"],t["./throwWrongKeyOnActionCollectionConfig.js"],t["./isRecord.js"],t["json-storage-formatter/isFunction"],t["./shallowCompare.js"]);
|
package/createContext.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var e,t;e=this,t=(e,t,r,o,a)=>(()=>{"use strict";var s={
|
|
1
|
+
var e,t;e=this,t=(e,t,r,o,a)=>(()=>{"use strict";var s={12:t=>{t.exports=e},78:e=>{e.exports=t},506:e=>{e.exports=r},695:function(e,t,r){var o=this&&this.__rest||function(e,t){var r={};for(var o in e)Object.prototype.hasOwnProperty.call(e,o)&&t.indexOf(o)<0&&(r[o]=e[o]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols){var a=0;for(o=Object.getOwnPropertySymbols(e);a<o.length;a++)t.indexOf(o[a])<0&&Object.prototype.propertyIsEnumerable.call(e,o[a])&&(r[o[a]]=e[o[a]])}return r},a=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.createContext=void 0;const s=r(12),n=r(778),l=a(r(506)),u=a(r(773)),i=a(r(78));function c(e,t){var r;const o=null!==(r=null==t?void 0:t.name)&&void 0!==r?r:(0,i.default)("sh:"),a=(...r)=>{const a=this.api();return(0,s.useDebugValue)(`${this.displayName}:selector`),(0,s.useDebugValue)(`${o}:use`),(0,s.useMemo)((()=>a.createSelectorHook(e,t)),[a])(...r)},l={displayName:this.displayName,createSelectorHook:c.bind(a),api:()=>{(0,s.useDebugValue)(`${this.displayName}:selector`),(0,s.useDebugValue)(`${o}:api`);const r=this.api();return(0,s.useMemo)((()=>{const o=r.createObservable(e,t);return Object.assign(Object.assign({},r),{getState:()=>o.getState(),subscribe:o.subscribe.bind(o),createSelectorHook:n.createSelectorHook.bind(o),createObservable:n.createObservable.bind(o)})}),[r])}};return Object.assign(a,l),a}t.createContext=(e,t={})=>{var r;const a=(0,s.createContext)(null),d=null!==(r=t.name)&&void 0!==r?r:(0,i.default)("ctx:"),v=r=>{const i=(r=>{var{value:a}=r,i=o(r,["value"]);(0,s.useDebugValue)(`${d}:Provider`);const c=(0,s.useMemo)((()=>{var r,o,s,c;const d=()=>(0,l.default)(e)?e():e,v=(0,u.default)(a)?d():(0,l.default)(a)?a(d()):a,b=new n.GlobalStore(v,Object.assign(Object.assign({},t),{metadata:null!==(r=(0,l.default)(t.metadata)?t.metadata():t.metadata)&&void 0!==r?r:{}})),f={use:p};return Object.assign(b.storeTools,f),null===(s=null===(o=t.callbacks)||void 0===o?void 0:o.onCreated)||void 0===s||s.call(o,b.storeTools,b),null===(c=i.onCreated)||void 0===c||c.call(i,b.storeTools,b),b}),[]);return(0,s.useEffect)((()=>{var e,r,o;const a=[null===(r=null===(e=t.callbacks)||void 0===e?void 0:e.onMounted)||void 0===r?void 0:r.call(e,c.storeTools,c),null===(o=i.onMounted)||void 0===o?void 0:o.call(i,c.storeTools,c)].filter(Boolean);return()=>{var e,t,r,o;null===(t=null===(e=c.callbacks)||void 0===e?void 0:e.onUnMount)||void 0===t||t.call(e,c),a.forEach((e=>null==e?void 0:e())),null===(o=(r=c).__onUnMountContext)||void 0===o||o.call(r,c)}}),[c]),c})(r);return(0,s.createElement)(a.Provider,{value:i.use},r.children)};v.displayName=`${d}:Provider`,a.displayName=`${d}`;const b={makeProviderWrapper:e=>{const t={current:void 0,instance:void 0};return{wrapper:r=>{var{children:a}=r,n=o(r,["children"]);return(0,s.createElement)(v,Object.assign(Object.assign(Object.assign({},e),n),{onCreated:(r,o)=>{var a;t.current=r,t.instance=o,null===(a=null==e?void 0:e.onCreated)||void 0===a||a.call(e,r,o)}}),a)},context:t}}},p=(...e)=>{(0,s.useDebugValue)(`${d}:use`);const t=(0,s.useContext)(a);if(!t)throw new Error("use hook must be used within a ContextProvider");return t(...e)},f=()=>{(0,s.useDebugValue)(`${d}:api`);const e=(0,s.useContext)(a);if(!e)throw new Error("api hook must be used within a ContextProvider");return e},m={displayName:a.displayName,createSelectorHook:c.bind(p),api:f,select:(...e)=>p(...e)[0],observable:(...e)=>{(0,s.useDebugValue)(`${d}:observable`);const t=f(),r=(0,s.useRef)(e);return r.current=e,(0,s.useMemo)((()=>{var e,o,a;return t.createObservable((e=>(0,r.current[0])(e)),{isEqualRoot:(null===(e=r.current[1])||void 0===e?void 0:e.isEqualRoot)?(e,t)=>{const o=r.current[1].isEqualRoot;return Boolean(o(e,t))}:void 0,isEqual:(null===(o=r.current[1])||void 0===o?void 0:o.isEqual)?(e,t)=>{const o=r.current[1].isEqual;return Boolean(o(e,t))}:void 0,name:null===(a=r.current[1])||void 0===a?void 0:a.name})}),[t])},actions:()=>f().actions};return Object.assign(v,b),Object.assign(p,m),{use:p,Provider:v,Context:a}},t.default=t.createContext},773:e=>{e.exports=o},778:e=>{e.exports=a}},n={};return function e(t){var r=n[t];if(void 0!==r)return r.exports;var o=n[t]={exports:{}};return s[t].call(o.exports,o,o.exports,e),o.exports}(695)})(),"object"==typeof exports&&"object"==typeof module?module.exports=t(require("react"),require("./uniqueId.js"),require("json-storage-formatter/isFunction"),require("json-storage-formatter/isNil"),require("./GlobalStore.js")):"function"==typeof define&&define.amd?define(["react","./uniqueId.js","json-storage-formatter/isFunction","json-storage-formatter/isNil","./GlobalStore.js"],t):"object"==typeof exports?exports["react-hooks-global-states"]=t(require("react"),require("./uniqueId.js"),require("json-storage-formatter/isFunction"),require("json-storage-formatter/isNil"),require("./GlobalStore.js")):e["react-hooks-global-states"]=t(e.React,e["./uniqueId.js"],e["json-storage-formatter/isFunction"],e["json-storage-formatter/isNil"],e["./GlobalStore.js"]);
|
package/createGlobalState.d.ts
CHANGED
package/package.json
CHANGED
package/webpack.config.js
CHANGED
|
@@ -29,7 +29,12 @@ module.exports = {
|
|
|
29
29
|
...individualEntries,
|
|
30
30
|
},
|
|
31
31
|
externals: {
|
|
32
|
-
react:
|
|
32
|
+
react: {
|
|
33
|
+
commonjs: 'react',
|
|
34
|
+
commonjs2: 'react',
|
|
35
|
+
amd: 'react',
|
|
36
|
+
root: 'React',
|
|
37
|
+
},
|
|
33
38
|
|
|
34
39
|
'json-storage-formatter': 'json-storage-formatter',
|
|
35
40
|
'json-storage-formatter/isNil': 'json-storage-formatter/isNil',
|
package/README.proposal.md
DELETED
|
@@ -1,1191 +0,0 @@
|
|
|
1
|
-
# react-hooks-global-states 🌟
|
|
2
|
-
|
|
3
|
-

|
|
4
|
-
|
|
5
|
-
**Effortless global state management for React, React Native, and Preact!** 🚀
|
|
6
|
-
|
|
7
|
-
Define **global state in just one line of code** and enjoy **lightweight, flexible, and scalable** state management with the familiarity of `useState`, but with the power of Redux and the simplicity of hooks. Zero configuration, fully typed, and framework-agnostic! ✨
|
|
8
|
-
|
|
9
|
-
```tsx
|
|
10
|
-
import { createGlobalState } from 'react-hooks-global-states/createGlobalState';
|
|
11
|
-
|
|
12
|
-
export const useCounter = createGlobalState(0);
|
|
13
|
-
|
|
14
|
-
// That's it! Use it anywhere in your app 🎉
|
|
15
|
-
function Counter() {
|
|
16
|
-
const [count, setCount] = useCounter();
|
|
17
|
-
return <button onClick={() => setCount(count + 1)}>{count}</button>;
|
|
18
|
-
}
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
---
|
|
22
|
-
|
|
23
|
-
## 🔗 Explore More
|
|
24
|
-
|
|
25
|
-
- **[Live Example](https://johnny-quesada-developer.github.io/global-hooks-example/)** 📘
|
|
26
|
-
- **[Video Overview](https://www.youtube.com/watch?v=1UBqXk2MH8I/)** 🎥
|
|
27
|
-
- **[CodePen Demo](https://codepen.io/johnnynabetes/pen/WNmeGwb?editors=0010)** - Try it now!
|
|
28
|
-
- **[react-hooks-global-states](https://www.npmjs.com/package/react-hooks-global-states)** - Compatible with React & React Native
|
|
29
|
-
- **[react-global-state-hooks](https://www.npmjs.com/package/react-global-state-hooks)** - Web with **localStorage integration**
|
|
30
|
-
- **[react-native-global-state-hooks](https://www.npmjs.com/package/react-native-global-state-hooks)** - React Native with **AsyncStorage integration**
|
|
31
|
-
|
|
32
|
-
---
|
|
33
|
-
|
|
34
|
-
## 🚀 DevTools Extension
|
|
35
|
-
|
|
36
|
-
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 within your browser.
|
|
37
|
-
|
|
38
|
-
### 🔗 [Install the DevTools Extension for Chrome](https://chromewebstore.google.com/detail/bafojplmkpejhglhjpibpdhoblickpee/preview?hl=en&authuser=0)
|
|
39
|
-
|
|
40
|
-
### 📸 DevTools Highlights
|
|
41
|
-
|
|
42
|
-
| **Track State Changes** | **Modify the State** |
|
|
43
|
-
| ------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
|
|
44
|
-
|  |  |
|
|
45
|
-
| Effortlessly monitor state updates and history. | Instantly edit global states directly from the extension. |
|
|
46
|
-
|
|
47
|
-
---
|
|
48
|
-
|
|
49
|
-
| **Restore the State** | **Custom Actions Granularity** |
|
|
50
|
-
| --------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
51
|
-
|  |  |
|
|
52
|
-
| Quickly revert your application to a previous state. | Precisely debug specific actions affecting state changes. |
|
|
53
|
-
|
|
54
|
-
---
|
|
55
|
-
|
|
56
|
-
## 🎯 Why This Library?
|
|
57
|
-
|
|
58
|
-
| Feature | react-hooks-global-states | Redux | Zustand | Jotai |
|
|
59
|
-
| -------------------------- | -------------------------------------- | ---------------------- | ----------------- | ----------------------- |
|
|
60
|
-
| **Setup complexity** | ✅ One line | ❌ Boilerplate heavy | ✅ Minimal | ✅ Minimal |
|
|
61
|
-
| **TypeScript** | ✅ Full inference | ⚠️ Manual typing | ✅ Good | ✅ Good |
|
|
62
|
-
| **Selectors** | ✅ Built-in + chainable | ⚠️ Separate lib needed | ✅ Built-in | ✅ Built-in |
|
|
63
|
-
| **Actions/Reducers** | ✅ Optional, type-safe | ✅ Required | ✅ Optional | ❌ Not built-in |
|
|
64
|
-
| **DevTools** | ✅ Dedicated extension | ✅ Redux DevTools | ⚠️ Via middleware | ⚠️ Via additional setup |
|
|
65
|
-
| **Scoped state (Context)** | ✅ Built-in API | ❌ | ⚠️ Manual | ✅ Via atoms |
|
|
66
|
-
| **Observable patterns** | ✅ First-class | ❌ | ⚠️ Via subscribe | ⚠️ Custom |
|
|
67
|
-
| **Dependencies array** | ✅ Selector deps | ❌ | ❌ | ❌ |
|
|
68
|
-
| **Learning curve** | ✅ If you know useState, you know this | ❌ High | ✅ Low | ✅ Low |
|
|
69
|
-
|
|
70
|
-
---
|
|
71
|
-
|
|
72
|
-
## 📦 Installation
|
|
73
|
-
|
|
74
|
-
```bash
|
|
75
|
-
npm install react-hooks-global-states
|
|
76
|
-
# or
|
|
77
|
-
yarn add react-hooks-global-states
|
|
78
|
-
# or
|
|
79
|
-
pnpm add react-hooks-global-states
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
**Platform-specific packages with storage integration:**
|
|
83
|
-
|
|
84
|
-
- **[react-global-state-hooks](https://www.npmjs.com/package/react-global-state-hooks)** - Web with localStorage
|
|
85
|
-
- **[react-native-global-state-hooks](https://www.npmjs.com/package/react-native-global-state-hooks)** - React Native with AsyncStorage
|
|
86
|
-
|
|
87
|
-
---
|
|
88
|
-
|
|
89
|
-
## 🚀 Quick Start
|
|
90
|
-
|
|
91
|
-
### Basic Usage
|
|
92
|
-
|
|
93
|
-
```tsx
|
|
94
|
-
import { createGlobalState } from 'react-hooks-global-states/createGlobalState';
|
|
95
|
-
|
|
96
|
-
// Create a global state
|
|
97
|
-
const useAuth = createGlobalState<{ user: User | null }>({ user: null });
|
|
98
|
-
|
|
99
|
-
// Use it in any component
|
|
100
|
-
function UserProfile() {
|
|
101
|
-
const [auth, setAuth] = useAuth();
|
|
102
|
-
|
|
103
|
-
if (!auth.user) return <Login />;
|
|
104
|
-
|
|
105
|
-
return <div>Welcome {auth.user.name}</div>;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function LoginButton() {
|
|
109
|
-
const [, setAuth] = useAuth();
|
|
110
|
-
|
|
111
|
-
return <button onClick={() => setAuth({ user: { name: 'John' } })}>Login</button>;
|
|
112
|
-
}
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
---
|
|
116
|
-
|
|
117
|
-
## 🎛️ Core Features
|
|
118
|
-
|
|
119
|
-
### 1️⃣ Selectors - Subscribe to Specific State Changes
|
|
120
|
-
|
|
121
|
-
For complex state objects, selectors allow components to subscribe only to the parts of state they care about, preventing unnecessary re-renders.
|
|
122
|
-
|
|
123
|
-
```tsx
|
|
124
|
-
import { createGlobalState } from 'react-hooks-global-states/createGlobalState';
|
|
125
|
-
|
|
126
|
-
const useContacts = createGlobalState({
|
|
127
|
-
items: [] as Contact[],
|
|
128
|
-
filter: '',
|
|
129
|
-
selectedIds: new Set<number>(),
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
// Component only re-renders when items change
|
|
133
|
-
function ContactList() {
|
|
134
|
-
const [contacts] = useContacts((state) => state.items);
|
|
135
|
-
|
|
136
|
-
return (
|
|
137
|
-
<ul>
|
|
138
|
-
{contacts.map((contact) => (
|
|
139
|
-
<li key={contact.id}>{contact.name}</li>
|
|
140
|
-
))}
|
|
141
|
-
</ul>
|
|
142
|
-
);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// Component only re-renders when filter changes
|
|
146
|
-
function SearchBar() {
|
|
147
|
-
const [filter, setContacts] = useContacts(
|
|
148
|
-
(state) => state.filter,
|
|
149
|
-
// Second parameter can customize equality checks
|
|
150
|
-
{ isEqualRoot: (a, b) => a.filter === b.filter },
|
|
151
|
-
);
|
|
152
|
-
|
|
153
|
-
return (
|
|
154
|
-
<input value={filter} onChange={(e) => setContacts((prev) => ({ ...prev, filter: e.target.value }))} />
|
|
155
|
-
);
|
|
156
|
-
}
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
### 2️⃣ Derived Selectors with Dependencies
|
|
160
|
-
|
|
161
|
-
Unlike Redux, derived values automatically recompute when dependencies change while maintaining optimal performance.
|
|
162
|
-
|
|
163
|
-
```tsx
|
|
164
|
-
function FilteredContacts() {
|
|
165
|
-
const [filter] = useContacts((state) => state.filter);
|
|
166
|
-
|
|
167
|
-
// This selector recomputes when filter dependency changes
|
|
168
|
-
const [filtered] = useContacts(
|
|
169
|
-
(state) => state.items.filter((c) => c.name.includes(filter)),
|
|
170
|
-
[filter], // Dependencies array
|
|
171
|
-
);
|
|
172
|
-
|
|
173
|
-
return <ContactList contacts={filtered} />;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// Or use options object for more control
|
|
177
|
-
function FilteredContactsAdvanced() {
|
|
178
|
-
const [filter] = useContacts((state) => state.filter);
|
|
179
|
-
|
|
180
|
-
const [filtered] = useContacts((state) => state.items.filter((c) => c.name.includes(filter)), {
|
|
181
|
-
dependencies: [filter],
|
|
182
|
-
isEqualRoot: (a, b) => a.items === b.items,
|
|
183
|
-
isEqual: (a, b) => a.length === b.length,
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
return <ContactList contacts={filtered} />;
|
|
187
|
-
}
|
|
188
|
-
```
|
|
189
|
-
|
|
190
|
-
### 3️⃣ Reusable Selector Hooks
|
|
191
|
-
|
|
192
|
-
Create derived hooks that can be reused across components and chained together.
|
|
193
|
-
|
|
194
|
-
```tsx
|
|
195
|
-
// Create reusable selectors
|
|
196
|
-
const useContactsArray = useContacts.createSelectorHook((state) => state.items);
|
|
197
|
-
|
|
198
|
-
const useContactsCount = useContactsArray.createSelectorHook((items) => items.length);
|
|
199
|
-
|
|
200
|
-
const useFilteredContacts = useContactsArray.createSelectorHook((items, filter) =>
|
|
201
|
-
items.filter((c) => c.name.includes(filter)),
|
|
202
|
-
);
|
|
203
|
-
|
|
204
|
-
// Use them in components
|
|
205
|
-
function Stats() {
|
|
206
|
-
const [count] = useContactsCount();
|
|
207
|
-
return <div>Total contacts: {count}</div>;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
function ContactList() {
|
|
211
|
-
const [filter] = useContacts((state) => state.filter);
|
|
212
|
-
|
|
213
|
-
// Selector hooks still support inline selectors with dependencies!
|
|
214
|
-
const [filtered] = useContactsArray(
|
|
215
|
-
(contacts) => contacts.filter((c) => c.name.includes(filter)),
|
|
216
|
-
[filter],
|
|
217
|
-
);
|
|
218
|
-
|
|
219
|
-
return <ul>{/* ... */}</ul>;
|
|
220
|
-
}
|
|
221
|
-
```
|
|
222
|
-
|
|
223
|
-
**✅ Important:** All selector hooks share the same state mutator, ensuring consistency:
|
|
224
|
-
|
|
225
|
-
```tsx
|
|
226
|
-
const [, setContactsArray] = useContactsArray();
|
|
227
|
-
const [, setContactsOriginal] = useContacts();
|
|
228
|
-
|
|
229
|
-
// These are the same function!
|
|
230
|
-
console.log(setContactsArray === setContactsOriginal); // true
|
|
231
|
-
console.log(setContactsArray === useContacts.setState); // true
|
|
232
|
-
```
|
|
233
|
-
|
|
234
|
-
### 4️⃣ Actions - Structured State Mutations
|
|
235
|
-
|
|
236
|
-
Restrict state modifications to predefined actions for better architecture and type safety.
|
|
237
|
-
|
|
238
|
-
```tsx
|
|
239
|
-
import { createGlobalState } from 'react-hooks-global-states/createGlobalState';
|
|
240
|
-
|
|
241
|
-
const useContacts = createGlobalState(
|
|
242
|
-
{ items: [], filter: '', loading: false },
|
|
243
|
-
{
|
|
244
|
-
actions: {
|
|
245
|
-
// Async actions are fully supported
|
|
246
|
-
async fetch() {
|
|
247
|
-
return async ({ setState, getState }) => {
|
|
248
|
-
setState((prev) => ({ ...prev, loading: true }));
|
|
249
|
-
|
|
250
|
-
try {
|
|
251
|
-
const items = await fetchContacts();
|
|
252
|
-
setState((prev) => ({ ...prev, items, loading: false }));
|
|
253
|
-
} catch (error) {
|
|
254
|
-
setState((prev) => ({ ...prev, loading: false }));
|
|
255
|
-
}
|
|
256
|
-
};
|
|
257
|
-
},
|
|
258
|
-
|
|
259
|
-
// Parameterized actions
|
|
260
|
-
setFilter(filter: string) {
|
|
261
|
-
return ({ setState }) => {
|
|
262
|
-
setState((prev) => ({ ...prev, filter }));
|
|
263
|
-
};
|
|
264
|
-
},
|
|
265
|
-
|
|
266
|
-
// Actions can access other actions
|
|
267
|
-
addContact(contact: Contact) {
|
|
268
|
-
return ({ setState, actions }) => {
|
|
269
|
-
setState((prev) => ({
|
|
270
|
-
...prev,
|
|
271
|
-
items: [...prev.items, contact],
|
|
272
|
-
}));
|
|
273
|
-
|
|
274
|
-
// Clear filter after adding
|
|
275
|
-
actions.setFilter('');
|
|
276
|
-
};
|
|
277
|
-
},
|
|
278
|
-
},
|
|
279
|
-
},
|
|
280
|
-
);
|
|
281
|
-
|
|
282
|
-
// When actions are defined, the second element is the actions object
|
|
283
|
-
function ContactManager() {
|
|
284
|
-
const [state, actions] = useContacts();
|
|
285
|
-
|
|
286
|
-
useEffect(() => {
|
|
287
|
-
actions.fetch();
|
|
288
|
-
}, []);
|
|
289
|
-
|
|
290
|
-
return (
|
|
291
|
-
<div>
|
|
292
|
-
{state.loading && <Spinner />}
|
|
293
|
-
<input value={state.filter} onChange={(e) => actions.setFilter(e.target.value)} />
|
|
294
|
-
<button onClick={() => actions.addContact(newContact)}>Add Contact</button>
|
|
295
|
-
</div>
|
|
296
|
-
);
|
|
297
|
-
}
|
|
298
|
-
```
|
|
299
|
-
|
|
300
|
-
**Note:** When actions are defined, direct `setState` is replaced by the actions object. However, `setState` is still available via the API for testing: `useContacts.setState()`
|
|
301
|
-
|
|
302
|
-
---
|
|
303
|
-
|
|
304
|
-
## � Action Groups with `actionsFor`
|
|
305
|
-
|
|
306
|
-
Need to **extend a store with additional actions** without modifying the original store definition? Use `actionsFor` to create action groups that can access both the store's state **and parent store actions**! Perfect for composition and code organization! 🎉
|
|
307
|
-
|
|
308
|
-
### 📌 Direct Binding
|
|
309
|
-
|
|
310
|
-
Bind actions directly to a store:
|
|
311
|
-
|
|
312
|
-
```tsx
|
|
313
|
-
import { createGlobalState } from 'react-hooks-global-states/createGlobalState';
|
|
314
|
-
import { actionsFor } from 'react-hooks-global-states/actionsFor';
|
|
315
|
-
|
|
316
|
-
const useCounter = createGlobalState(0);
|
|
317
|
-
|
|
318
|
-
const counterActions = actionsFor(useCounter.api(), {
|
|
319
|
-
increment() {
|
|
320
|
-
return ({ setState, getState }) => {
|
|
321
|
-
setState(getState() + 1);
|
|
322
|
-
};
|
|
323
|
-
},
|
|
324
|
-
decrement() {
|
|
325
|
-
return ({ setState, getState }) => {
|
|
326
|
-
setState(getState() - 1);
|
|
327
|
-
};
|
|
328
|
-
},
|
|
329
|
-
reset() {
|
|
330
|
-
return ({ setState }) => {
|
|
331
|
-
setState(0);
|
|
332
|
-
};
|
|
333
|
-
},
|
|
334
|
-
});
|
|
335
|
-
|
|
336
|
-
// Use them anywhere in your app!
|
|
337
|
-
counterActions.increment();
|
|
338
|
-
counterActions.decrement();
|
|
339
|
-
counterActions.reset();
|
|
340
|
-
```
|
|
341
|
-
|
|
342
|
-
### 📌 Builder Pattern (Reusable Templates!)
|
|
343
|
-
|
|
344
|
-
Create **reusable action templates** that can be applied to multiple stores. This is incredibly powerful for code reuse! 💪
|
|
345
|
-
|
|
346
|
-
```tsx
|
|
347
|
-
import { actionsFor } from 'react-hooks-global-states/actionsFor';
|
|
348
|
-
|
|
349
|
-
// Create a reusable template
|
|
350
|
-
const withCounterActions = actionsFor().with({
|
|
351
|
-
increment() {
|
|
352
|
-
return ({ setState, getState }) => {
|
|
353
|
-
setState(getState() + 1);
|
|
354
|
-
};
|
|
355
|
-
},
|
|
356
|
-
decrement() {
|
|
357
|
-
return ({ setState, getState }) => {
|
|
358
|
-
setState(getState() - 1);
|
|
359
|
-
};
|
|
360
|
-
},
|
|
361
|
-
incrementBy(amount: number) {
|
|
362
|
-
return ({ setState, getState }) => {
|
|
363
|
-
setState(getState() + amount);
|
|
364
|
-
};
|
|
365
|
-
},
|
|
366
|
-
});
|
|
367
|
-
|
|
368
|
-
// Apply it to different stores! 🚀
|
|
369
|
-
const counter1Actions = withCounterActions(useCounter1.api());
|
|
370
|
-
const counter2Actions = withCounterActions(useCounter2.api());
|
|
371
|
-
|
|
372
|
-
counter1Actions.increment(); // Updates counter1
|
|
373
|
-
counter2Actions.incrementBy(5); // Updates counter2 by 5
|
|
374
|
-
```
|
|
375
|
-
|
|
376
|
-
### 📌 Accessing Parent Actions
|
|
377
|
-
|
|
378
|
-
Actions created with `actionsFor` can call **parent store actions** via `this.actions`. This enables powerful composition patterns! 🔗
|
|
379
|
-
|
|
380
|
-
```tsx
|
|
381
|
-
import { createGlobalState } from 'react-hooks-global-states/createGlobalState';
|
|
382
|
-
import { actionsFor } from 'react-hooks-global-states/actionsFor';
|
|
383
|
-
|
|
384
|
-
const useCounter = createGlobalState(0, {
|
|
385
|
-
actions: {
|
|
386
|
-
reset() {
|
|
387
|
-
return ({ setState }) => setState(0);
|
|
388
|
-
},
|
|
389
|
-
setTo(value: number) {
|
|
390
|
-
return ({ setState }) => setState(value);
|
|
391
|
-
},
|
|
392
|
-
},
|
|
393
|
-
});
|
|
394
|
-
|
|
395
|
-
// Extend with additional actions that can use parent actions
|
|
396
|
-
const extendedActions = actionsFor(useCounter.api(), {
|
|
397
|
-
incrementAndLog() {
|
|
398
|
-
return ({ setState, getState }) => {
|
|
399
|
-
const newValue = getState() + 1;
|
|
400
|
-
setState(newValue);
|
|
401
|
-
console.log(`Incremented to ${newValue}`);
|
|
402
|
-
};
|
|
403
|
-
},
|
|
404
|
-
resetWithMessage() {
|
|
405
|
-
return function () {
|
|
406
|
-
// Call parent action! ✨
|
|
407
|
-
this.actions.reset();
|
|
408
|
-
console.log('Counter has been reset!');
|
|
409
|
-
};
|
|
410
|
-
},
|
|
411
|
-
doubleIt() {
|
|
412
|
-
return function ({ getState }) {
|
|
413
|
-
// Access parent actions and use them
|
|
414
|
-
const current = getState();
|
|
415
|
-
this.actions.setTo(current * 2);
|
|
416
|
-
};
|
|
417
|
-
},
|
|
418
|
-
});
|
|
419
|
-
|
|
420
|
-
extendedActions.resetWithMessage(); // Calls parent reset() and logs message
|
|
421
|
-
extendedActions.doubleIt(); // Doubles the counter using parent setTo()
|
|
422
|
-
```
|
|
423
|
-
|
|
424
|
-
### 📌 Complex Example: Async Actions with Dependencies
|
|
425
|
-
|
|
426
|
-
```tsx
|
|
427
|
-
import { createGlobalState } from 'react-hooks-global-states/createGlobalState';
|
|
428
|
-
import { actionsFor } from 'react-hooks-global-states/actionsFor';
|
|
429
|
-
|
|
430
|
-
const useTodos = createGlobalState(
|
|
431
|
-
{ items: [], loading: false },
|
|
432
|
-
{
|
|
433
|
-
actions: {
|
|
434
|
-
setLoading(loading: boolean) {
|
|
435
|
-
return ({ setState, getState }) => {
|
|
436
|
-
setState({ ...getState(), loading });
|
|
437
|
-
};
|
|
438
|
-
},
|
|
439
|
-
},
|
|
440
|
-
},
|
|
441
|
-
);
|
|
442
|
-
|
|
443
|
-
const todoActions = actionsFor(useTodos.api(), {
|
|
444
|
-
async fetchTodos() {
|
|
445
|
-
return async function ({ setState, getState }) {
|
|
446
|
-
// Use parent action
|
|
447
|
-
this.actions.setLoading(true);
|
|
448
|
-
|
|
449
|
-
try {
|
|
450
|
-
const items = await fetch('/api/todos').then((r) => r.json());
|
|
451
|
-
setState({ ...getState(), items, loading: false });
|
|
452
|
-
} catch (error) {
|
|
453
|
-
this.actions.setLoading(false);
|
|
454
|
-
throw error;
|
|
455
|
-
}
|
|
456
|
-
};
|
|
457
|
-
},
|
|
458
|
-
|
|
459
|
-
addTodo(text: string) {
|
|
460
|
-
return ({ setState, getState }) => {
|
|
461
|
-
const newTodo = { id: Date.now(), text, completed: false };
|
|
462
|
-
const items = [...getState().items, newTodo];
|
|
463
|
-
setState({ ...getState(), items });
|
|
464
|
-
};
|
|
465
|
-
},
|
|
466
|
-
|
|
467
|
-
async addAndRefresh(text: string) {
|
|
468
|
-
return async function () {
|
|
469
|
-
// Compose multiple actions! 🎯
|
|
470
|
-
this.addTodo(text);
|
|
471
|
-
await this.fetchTodos();
|
|
472
|
-
};
|
|
473
|
-
},
|
|
474
|
-
});
|
|
475
|
-
|
|
476
|
-
// Use them!
|
|
477
|
-
await todoActions.fetchTodos();
|
|
478
|
-
todoActions.addTodo('Learn actionsFor');
|
|
479
|
-
await todoActions.addAndRefresh('This is awesome!');
|
|
480
|
-
```
|
|
481
|
-
|
|
482
|
-
### ✨ Why Use `actionsFor`?
|
|
483
|
-
|
|
484
|
-
- **🔄 Composability** – Mix and match action groups across stores without modifying original definitions
|
|
485
|
-
- **📦 Reusability** – Define action templates once, use them everywhere
|
|
486
|
-
- **🔗 Access Parent Actions** – New actions can leverage existing store logic via `this.actions`
|
|
487
|
-
- **💪 Type Safety** – Full TypeScript inference for actions, parameters, and state
|
|
488
|
-
- **🎯 Separation of Concerns** – Keep core store simple, extend with specialized action groups
|
|
489
|
-
- **🚀 Progressive Enhancement** – Start simple, add complexity as needed
|
|
490
|
-
|
|
491
|
-
---
|
|
492
|
-
|
|
493
|
-
## �🌐 Non-Component Usage
|
|
494
|
-
|
|
495
|
-
Access and manipulate state outside React components - perfect for services, utilities, and event handlers.
|
|
496
|
-
|
|
497
|
-
```tsx
|
|
498
|
-
import { createGlobalState } from 'react-hooks-global-states/createGlobalState';
|
|
499
|
-
|
|
500
|
-
const useAuth = createGlobalState({ user: null, token: null });
|
|
501
|
-
|
|
502
|
-
// API service
|
|
503
|
-
class AuthService {
|
|
504
|
-
async login(credentials: Credentials) {
|
|
505
|
-
const { user, token } = await api.login(credentials);
|
|
506
|
-
|
|
507
|
-
// Direct state access
|
|
508
|
-
useAuth.setState({ user, token });
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
async logout() {
|
|
512
|
-
await api.logout();
|
|
513
|
-
useAuth.setState({ user: null, token: null });
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
getToken() {
|
|
517
|
-
// Get current state synchronously
|
|
518
|
-
return useAuth.getState().token;
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
// Subscribe to changes outside components
|
|
523
|
-
const unsubscribe = useAuth.subscribe((state) => {
|
|
524
|
-
console.log('Auth state changed:', state);
|
|
525
|
-
|
|
526
|
-
// Sync to external storage
|
|
527
|
-
if (state.token) {
|
|
528
|
-
localStorage.setItem('token', state.token);
|
|
529
|
-
}
|
|
530
|
-
});
|
|
531
|
-
|
|
532
|
-
// Later: cleanup
|
|
533
|
-
unsubscribe();
|
|
534
|
-
```
|
|
535
|
-
|
|
536
|
-
### Subscriptions with Selectors
|
|
537
|
-
|
|
538
|
-
Subscriptions support the same selector API as hooks:
|
|
539
|
-
|
|
540
|
-
```tsx
|
|
541
|
-
// Subscribe to a specific part of state
|
|
542
|
-
const unsubscribe = useAuth.subscribe(
|
|
543
|
-
(state) => state.user,
|
|
544
|
-
(user) => {
|
|
545
|
-
console.log('User changed:', user);
|
|
546
|
-
},
|
|
547
|
-
);
|
|
548
|
-
|
|
549
|
-
// With options
|
|
550
|
-
const unsubscribe2 = useAuth.subscribe(
|
|
551
|
-
(state) => state.user,
|
|
552
|
-
(user) => {
|
|
553
|
-
analytics.identify(user);
|
|
554
|
-
},
|
|
555
|
-
{
|
|
556
|
-
skipFirst: true, // Don't call on initial subscribe
|
|
557
|
-
isEqual: (a, b) => a?.id === b?.id,
|
|
558
|
-
},
|
|
559
|
-
);
|
|
560
|
-
```
|
|
561
|
-
|
|
562
|
-
### Cross-State Dependencies
|
|
563
|
-
|
|
564
|
-
One global state can react to changes in another:
|
|
565
|
-
|
|
566
|
-
```tsx
|
|
567
|
-
import { createGlobalState } from 'react-hooks-global-states/createGlobalState';
|
|
568
|
-
|
|
569
|
-
const useSelectedContact = createGlobalState<Contact | null>(null, {
|
|
570
|
-
callbacks: {
|
|
571
|
-
onInit: ({ setState, getState }) => {
|
|
572
|
-
// When contacts change, clear selection if contact was deleted
|
|
573
|
-
return useContacts.subscribe(
|
|
574
|
-
(state) => state.items,
|
|
575
|
-
(contacts) => {
|
|
576
|
-
const selected = getState();
|
|
577
|
-
if (selected && !contacts.find((c) => c.id === selected.id)) {
|
|
578
|
-
setState(null);
|
|
579
|
-
}
|
|
580
|
-
},
|
|
581
|
-
);
|
|
582
|
-
},
|
|
583
|
-
},
|
|
584
|
-
});
|
|
585
|
-
```
|
|
586
|
-
|
|
587
|
-
---
|
|
588
|
-
|
|
589
|
-
## 🎭 Context API - Scoped State
|
|
590
|
-
|
|
591
|
-
When you need state scoped to a component tree instead of globally available. Same powerful API, different scope! 🎯
|
|
592
|
-
|
|
593
|
-
```tsx
|
|
594
|
-
import { createContext } from 'react-hooks-global-states/createContext';
|
|
595
|
-
|
|
596
|
-
// Create a context with initial state
|
|
597
|
-
const TodoListContext = createContext(
|
|
598
|
-
{ todos: [], filter: 'all' },
|
|
599
|
-
{
|
|
600
|
-
actions: {
|
|
601
|
-
addTodo(text: string) {
|
|
602
|
-
return ({ setState }) => {
|
|
603
|
-
const newTodo = { id: Date.now(), text, completed: false };
|
|
604
|
-
setState((prev) => ({
|
|
605
|
-
...prev,
|
|
606
|
-
todos: [...prev.todos, newTodo],
|
|
607
|
-
}));
|
|
608
|
-
};
|
|
609
|
-
},
|
|
610
|
-
|
|
611
|
-
toggleTodo(id: number) {
|
|
612
|
-
return ({ setState }) => {
|
|
613
|
-
setState((prev) => ({
|
|
614
|
-
...prev,
|
|
615
|
-
todos: prev.todos.map((t) => (t.id === id ? { ...t, completed: !t.completed } : t)),
|
|
616
|
-
}));
|
|
617
|
-
};
|
|
618
|
-
},
|
|
619
|
-
|
|
620
|
-
setFilter(filter: 'all' | 'active' | 'completed') {
|
|
621
|
-
return ({ setState }) => {
|
|
622
|
-
setState((prev) => ({ ...prev, filter }));
|
|
623
|
-
};
|
|
624
|
-
},
|
|
625
|
-
},
|
|
626
|
-
},
|
|
627
|
-
);
|
|
628
|
-
|
|
629
|
-
// Wrap your component tree
|
|
630
|
-
function App() {
|
|
631
|
-
return (
|
|
632
|
-
<TodoListContext.Provider>
|
|
633
|
-
<TodoList />
|
|
634
|
-
<TodoStats />
|
|
635
|
-
</TodoListContext.Provider>
|
|
636
|
-
);
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
// Use the context hook
|
|
640
|
-
function TodoList() {
|
|
641
|
-
const [state, actions] = TodoListContext.use();
|
|
642
|
-
|
|
643
|
-
return (
|
|
644
|
-
<div>
|
|
645
|
-
<input
|
|
646
|
-
onKeyPress={(e) => {
|
|
647
|
-
if (e.key === 'Enter') {
|
|
648
|
-
actions.addTodo(e.currentTarget.value);
|
|
649
|
-
}
|
|
650
|
-
}}
|
|
651
|
-
/>
|
|
652
|
-
|
|
653
|
-
{state.todos.map((todo) => (
|
|
654
|
-
<div key={todo.id} onClick={() => actions.toggleTodo(todo.id)}>
|
|
655
|
-
{todo.text}
|
|
656
|
-
</div>
|
|
657
|
-
))}
|
|
658
|
-
</div>
|
|
659
|
-
);
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
// Access context API without triggering re-renders
|
|
663
|
-
function Logger() {
|
|
664
|
-
const api = TodoListContext.use.api();
|
|
665
|
-
|
|
666
|
-
useEffect(() => {
|
|
667
|
-
// This component won't re-render when state changes
|
|
668
|
-
const unsubscribe = api.subscribe((state) => {
|
|
669
|
-
console.log('State changed:', state);
|
|
670
|
-
});
|
|
671
|
-
|
|
672
|
-
return unsubscribe;
|
|
673
|
-
}, []);
|
|
674
|
-
|
|
675
|
-
return null;
|
|
676
|
-
}
|
|
677
|
-
```
|
|
678
|
-
|
|
679
|
-
### Context Features
|
|
680
|
-
|
|
681
|
-
All the same features as global state:
|
|
682
|
-
|
|
683
|
-
```tsx
|
|
684
|
-
// Selectors
|
|
685
|
-
const [todos] = TodoListContext.use((state) => state.todos);
|
|
686
|
-
|
|
687
|
-
// Selector hooks
|
|
688
|
-
const useTodos = TodoListContext.use.createSelectorHook((state) => state.todos);
|
|
689
|
-
const useTodoCount = useTodos.createSelectorHook((todos) => todos.length);
|
|
690
|
-
|
|
691
|
-
// Observables
|
|
692
|
-
const { context } = TodoListContext.Provider.makeProviderWrapper();
|
|
693
|
-
const todoObservable = context.current.createObservable((state) => state.todos);
|
|
694
|
-
|
|
695
|
-
// Access actions
|
|
696
|
-
const actions = TodoListContext.use.actions();
|
|
697
|
-
const [count, actions] = TodoListContext.use();
|
|
698
|
-
|
|
699
|
-
// Non-reactive API access
|
|
700
|
-
const api = TodoListContext.use.api();
|
|
701
|
-
api.getState();
|
|
702
|
-
api.setState({ todos: [], filter: 'all' });
|
|
703
|
-
api.subscribe((state) => console.log(state));
|
|
704
|
-
```
|
|
705
|
-
|
|
706
|
-
### Multiple Providers
|
|
707
|
-
|
|
708
|
-
Each provider creates an isolated state instance:
|
|
709
|
-
|
|
710
|
-
```tsx
|
|
711
|
-
function App() {
|
|
712
|
-
return (
|
|
713
|
-
<div>
|
|
714
|
-
<TodoListContext.Provider>
|
|
715
|
-
<TodoList title="Work Tasks" />
|
|
716
|
-
</TodoListContext.Provider>
|
|
717
|
-
|
|
718
|
-
<TodoListContext.Provider>
|
|
719
|
-
<TodoList title="Personal Tasks" />
|
|
720
|
-
</TodoListContext.Provider>
|
|
721
|
-
</div>
|
|
722
|
-
);
|
|
723
|
-
}
|
|
724
|
-
```
|
|
725
|
-
|
|
726
|
-
---
|
|
727
|
-
|
|
728
|
-
## 🔭 Observables - Reactive State Fragments
|
|
729
|
-
|
|
730
|
-
Observables provide a subscription-based API for watching specific state changes, useful for non-component code. Think of them as lightweight reactive streams! 🌊
|
|
731
|
-
|
|
732
|
-
```tsx
|
|
733
|
-
import { createGlobalState } from 'react-hooks-global-states/createGlobalState';
|
|
734
|
-
|
|
735
|
-
const useCounter = createGlobalState(0);
|
|
736
|
-
|
|
737
|
-
// Create an observable that transforms the state
|
|
738
|
-
const counterLogs = useCounter.createObservable((count) => `Counter is at ${count}`);
|
|
739
|
-
|
|
740
|
-
// Subscribe to changes
|
|
741
|
-
const unsubscribe = counterLogs((message) => {
|
|
742
|
-
console.log(message); // "Counter is at 0", "Counter is at 1", etc.
|
|
743
|
-
});
|
|
744
|
-
|
|
745
|
-
// Observables can be chained
|
|
746
|
-
const isEven = useCounter.createObservable((count) => count % 2 === 0);
|
|
747
|
-
const evenMessage = isEven.createObservable((even) => (even ? 'Even number' : 'Odd number'));
|
|
748
|
-
|
|
749
|
-
evenMessage((msg) => console.log(msg));
|
|
750
|
-
|
|
751
|
-
// Cleanup
|
|
752
|
-
unsubscribe();
|
|
753
|
-
```
|
|
754
|
-
|
|
755
|
-
### Observable API
|
|
756
|
-
|
|
757
|
-
Observables expose the same readonly API as hooks:
|
|
758
|
-
|
|
759
|
-
```tsx
|
|
760
|
-
const observable = useCounter.createObservable((count) => count * 2);
|
|
761
|
-
|
|
762
|
-
// Get current value
|
|
763
|
-
observable.getState(); // returns doubled count
|
|
764
|
-
|
|
765
|
-
// Subscribe to changes
|
|
766
|
-
const unsub = observable.subscribe((doubled) => {
|
|
767
|
-
console.log('Doubled value:', doubled);
|
|
768
|
-
});
|
|
769
|
-
|
|
770
|
-
// Create derived observables
|
|
771
|
-
const quadrupled = observable.createObservable((doubled) => doubled * 2);
|
|
772
|
-
|
|
773
|
-
// Create selector hooks from observables
|
|
774
|
-
const useDoubled = observable.createSelectorHook((state) => state);
|
|
775
|
-
|
|
776
|
-
// Cleanup
|
|
777
|
-
observable.dispose(); // Cleans up subscriptions
|
|
778
|
-
```
|
|
779
|
-
|
|
780
|
-
---
|
|
781
|
-
|
|
782
|
-
## 🔄 Lifecycle Callbacks
|
|
783
|
-
|
|
784
|
-
Hook into state initialization and changes for setup, cleanup, and validation. Perfect for side effects! ⚡
|
|
785
|
-
|
|
786
|
-
```tsx
|
|
787
|
-
import { createGlobalState } from 'react-hooks-global-states/createGlobalState';
|
|
788
|
-
|
|
789
|
-
const useData = createGlobalState(
|
|
790
|
-
{ value: 0, lastUpdated: null },
|
|
791
|
-
{
|
|
792
|
-
callbacks: {
|
|
793
|
-
// Called once when store initializes
|
|
794
|
-
onInit: ({ setState, subscribe }) => {
|
|
795
|
-
console.log('Store initialized');
|
|
796
|
-
|
|
797
|
-
// Return cleanup function
|
|
798
|
-
return () => {
|
|
799
|
-
console.log('Store cleaned up');
|
|
800
|
-
};
|
|
801
|
-
},
|
|
802
|
-
|
|
803
|
-
// Called after every state change
|
|
804
|
-
onStateChanged: ({ state, previousState, identifier }) => {
|
|
805
|
-
console.log('State changed:', previousState, '→', state);
|
|
806
|
-
|
|
807
|
-
if (identifier) {
|
|
808
|
-
console.log('Change identifier:', identifier);
|
|
809
|
-
}
|
|
810
|
-
|
|
811
|
-
// Update timestamp
|
|
812
|
-
setState((prev) => ({ ...prev, lastUpdated: Date.now() }));
|
|
813
|
-
},
|
|
814
|
-
|
|
815
|
-
// Prevent specific state changes
|
|
816
|
-
computePreventStateChange: ({ state, previousState }) => {
|
|
817
|
-
// Prevent setting value to same number
|
|
818
|
-
return state.value === previousState.value;
|
|
819
|
-
},
|
|
820
|
-
|
|
821
|
-
// Called when a component subscribes
|
|
822
|
-
onSubscribed: (storeTools, subscription) => {
|
|
823
|
-
console.log('New subscriber added');
|
|
824
|
-
},
|
|
825
|
-
},
|
|
826
|
-
},
|
|
827
|
-
);
|
|
828
|
-
|
|
829
|
-
// Trigger with identifier for debugging
|
|
830
|
-
useData.setState({ value: 5 }, { identifier: 'manual-update' });
|
|
831
|
-
|
|
832
|
-
// Force update even if state is the same
|
|
833
|
-
useData.setState((state) => state, { forceUpdate: true });
|
|
834
|
-
```
|
|
835
|
-
|
|
836
|
-
---
|
|
837
|
-
|
|
838
|
-
## 📊 Metadata - Non-Reactive Data
|
|
839
|
-
|
|
840
|
-
Store non-reactive information alongside your state without triggering re-renders. Great for tracking stats, timestamps, and other auxiliary data! 📈
|
|
841
|
-
|
|
842
|
-
```tsx
|
|
843
|
-
import { createGlobalState } from 'react-hooks-global-states/createGlobalState';
|
|
844
|
-
|
|
845
|
-
const useCounter = createGlobalState(0, {
|
|
846
|
-
metadata: {
|
|
847
|
-
createdAt: Date.now(),
|
|
848
|
-
updateCount: 0,
|
|
849
|
-
},
|
|
850
|
-
});
|
|
851
|
-
|
|
852
|
-
function Counter() {
|
|
853
|
-
const [count, setCount, metadata] = useCounter();
|
|
854
|
-
|
|
855
|
-
// Metadata doesn't cause re-renders when modified
|
|
856
|
-
metadata.updateCount += 1;
|
|
857
|
-
|
|
858
|
-
return (
|
|
859
|
-
<div>
|
|
860
|
-
<p>Count: {count}</p>
|
|
861
|
-
<p>Updates: {metadata.updateCount}</p>
|
|
862
|
-
<button onClick={() => setCount(count + 1)}>Increment</button>
|
|
863
|
-
</div>
|
|
864
|
-
);
|
|
865
|
-
}
|
|
866
|
-
|
|
867
|
-
// Access metadata externally
|
|
868
|
-
const meta = useCounter.getMetadata();
|
|
869
|
-
console.log(meta.createdAt);
|
|
870
|
-
|
|
871
|
-
// Update metadata
|
|
872
|
-
useCounter.setMetadata((prev) => ({
|
|
873
|
-
...prev,
|
|
874
|
-
lastAccessed: Date.now(),
|
|
875
|
-
}));
|
|
876
|
-
```
|
|
877
|
-
|
|
878
|
-
---
|
|
879
|
-
|
|
880
|
-
## 🧪 Testing & Debugging
|
|
881
|
-
|
|
882
|
-
### Reset State for Tests
|
|
883
|
-
|
|
884
|
-
```tsx
|
|
885
|
-
import { createGlobalState } from 'react-hooks-global-states/createGlobalState';
|
|
886
|
-
|
|
887
|
-
const useCounter = createGlobalState(0);
|
|
888
|
-
|
|
889
|
-
afterEach(() => {
|
|
890
|
-
// Reset to initial state
|
|
891
|
-
useCounter.reset(0, {});
|
|
892
|
-
});
|
|
893
|
-
|
|
894
|
-
test('counter increments', () => {
|
|
895
|
-
const { result } = renderHook(() => useCounter());
|
|
896
|
-
const [, setCount] = result.current;
|
|
897
|
-
|
|
898
|
-
act(() => setCount(1));
|
|
899
|
-
|
|
900
|
-
expect(result.current[0]).toBe(1);
|
|
901
|
-
});
|
|
902
|
-
```
|
|
903
|
-
|
|
904
|
-
### Dispose for Cleanup
|
|
905
|
-
|
|
906
|
-
```tsx
|
|
907
|
-
import { createGlobalState } from 'react-hooks-global-states/createGlobalState';
|
|
908
|
-
|
|
909
|
-
const useTemp = createGlobalState({ data: [] });
|
|
910
|
-
|
|
911
|
-
// When done with the store
|
|
912
|
-
useTemp.dispose(); // Clears all subscriptions and executes cleanup
|
|
913
|
-
```
|
|
914
|
-
|
|
915
|
-
### DevTools Extension
|
|
916
|
-
|
|
917
|
-
**[Install React Hooks Global States DevTools](https://chromewebstore.google.com/detail/bafojplmkpejhglhjpibpdhoblickpee)**
|
|
918
|
-
|
|
919
|
-
Features:
|
|
920
|
-
|
|
921
|
-
- 📊 Track all state changes in real-time
|
|
922
|
-
- ✏️ Modify state directly from the extension
|
|
923
|
-
- ⏮️ Time-travel debugging - restore previous states
|
|
924
|
-
- 🎯 Action granularity - see individual action executions
|
|
925
|
-
- 🔍 Inspect metadata and subscribers
|
|
926
|
-
|
|
927
|
-
### Debug Identifiers
|
|
928
|
-
|
|
929
|
-
```tsx
|
|
930
|
-
import { createGlobalState } from 'react-hooks-global-states/createGlobalState';
|
|
931
|
-
|
|
932
|
-
// Name your stores for easier debugging
|
|
933
|
-
const useAuth = createGlobalState({ user: null }, { name: 'AuthStore' });
|
|
934
|
-
|
|
935
|
-
// Use identifiers when updating state
|
|
936
|
-
useAuth.setState(
|
|
937
|
-
{ user: newUser },
|
|
938
|
-
{
|
|
939
|
-
identifier: 'login-success',
|
|
940
|
-
},
|
|
941
|
-
);
|
|
942
|
-
```
|
|
943
|
-
|
|
944
|
-
### Type Inference Helpers
|
|
945
|
-
|
|
946
|
-
```tsx
|
|
947
|
-
import { createGlobalState } from 'react-hooks-global-states/createGlobalState';
|
|
948
|
-
import type { InferAPI } from 'react-hooks-global-states/types';
|
|
949
|
-
|
|
950
|
-
const useStore = createGlobalState(/* ... */);
|
|
951
|
-
|
|
952
|
-
// Infer the complete API type
|
|
953
|
-
type StoreAPI = InferAPI<typeof useStore>;
|
|
954
|
-
|
|
955
|
-
// Use in functions that need the store tools
|
|
956
|
-
function doSomething(store: StoreAPI) {
|
|
957
|
-
store.getState();
|
|
958
|
-
store.setState(/* ... */);
|
|
959
|
-
store.subscribe(/* ... */);
|
|
960
|
-
}
|
|
961
|
-
```
|
|
962
|
-
|
|
963
|
-
---
|
|
964
|
-
|
|
965
|
-
## 📚 API Reference
|
|
966
|
-
|
|
967
|
-
### `createGlobalState(initialState, options?)`
|
|
968
|
-
|
|
969
|
-
Creates a global state hook.
|
|
970
|
-
|
|
971
|
-
**Parameters:**
|
|
972
|
-
|
|
973
|
-
- `initialState`: Initial state value
|
|
974
|
-
- `options?`: Configuration object
|
|
975
|
-
- `name?`: Debug name
|
|
976
|
-
- `metadata?`: Non-reactive metadata
|
|
977
|
-
- `callbacks?`: Lifecycle callbacks
|
|
978
|
-
- `onInit?`: Called on initialization
|
|
979
|
-
- `onStateChanged?`: Called after state changes
|
|
980
|
-
- `computePreventStateChange?`: Validation function
|
|
981
|
-
- `onSubscribed?`: Called when component subscribes
|
|
982
|
-
- `actions?`: Action definitions
|
|
983
|
-
|
|
984
|
-
**Returns:** State hook with extended API
|
|
985
|
-
|
|
986
|
-
**Hook API:**
|
|
987
|
-
|
|
988
|
-
- `use()` - Use in component (same as calling the hook directly)
|
|
989
|
-
- `select(selector, deps?)` - Select a value without subscribing to state changes
|
|
990
|
-
- `getState()` - Get current state
|
|
991
|
-
- `setState(update, options?)` - Update state
|
|
992
|
-
- `subscribe(callback)` / `subscribe(selector, callback, options?)` - Subscribe to changes
|
|
993
|
-
- `getMetadata()` - Get metadata
|
|
994
|
-
- `setMetadata(update)` - Update metadata
|
|
995
|
-
- `createSelectorHook(selector, options?)` - Create derived hook
|
|
996
|
-
- `createObservable(selector, options?)` - Create observable
|
|
997
|
-
- `reset(state, metadata)` - Reset store
|
|
998
|
-
- `dispose()` - Cleanup
|
|
999
|
-
- `actions` - Action methods (when actions defined)
|
|
1000
|
-
- `subscribers` - Active subscriptions (debugging)
|
|
1001
|
-
|
|
1002
|
-
### `createContext(initialState, options?)`
|
|
1003
|
-
|
|
1004
|
-
Creates a context with the same API as global state, but scoped to a Provider.
|
|
1005
|
-
|
|
1006
|
-
**Parameters:** Same as `createGlobalState`
|
|
1007
|
-
|
|
1008
|
-
**Returns:** Context object
|
|
1009
|
-
|
|
1010
|
-
- `Provider` - Context provider component
|
|
1011
|
-
- `makeProviderWrapper()` - Get provider wrapper and context ref
|
|
1012
|
-
- `use` - Use the context (must be inside Provider)
|
|
1013
|
-
- Same API as global state hook
|
|
1014
|
-
- `use.api()` - Get non-reactive API access
|
|
1015
|
-
- `use.actions()` - Get just the actions
|
|
1016
|
-
|
|
1017
|
-
---
|
|
1018
|
-
|
|
1019
|
-
## 💡 Best Practices
|
|
1020
|
-
|
|
1021
|
-
### 1. Organize State by Domain
|
|
1022
|
-
|
|
1023
|
-
```tsx
|
|
1024
|
-
import { createGlobalState } from 'react-hooks-global-states/createGlobalState';
|
|
1025
|
-
|
|
1026
|
-
// ✅ Good - domain-focused
|
|
1027
|
-
const useAuth = createGlobalState({ ... });
|
|
1028
|
-
const useCart = createGlobalState({ ... });
|
|
1029
|
-
const useProducts = createGlobalState({ ... });
|
|
1030
|
-
|
|
1031
|
-
// ❌ Avoid - one giant state
|
|
1032
|
-
const useAppState = createGlobalState({
|
|
1033
|
-
auth: { ... },
|
|
1034
|
-
cart: { ... },
|
|
1035
|
-
products: { ... },
|
|
1036
|
-
});
|
|
1037
|
-
```
|
|
1038
|
-
|
|
1039
|
-
### 2. Use Actions for Complex Logic
|
|
1040
|
-
|
|
1041
|
-
```tsx
|
|
1042
|
-
import { createGlobalState } from 'react-hooks-global-states/createGlobalState';
|
|
1043
|
-
|
|
1044
|
-
// ✅ Good - encapsulated business logic
|
|
1045
|
-
const useCart = createGlobalState(initialCart, {
|
|
1046
|
-
actions: {
|
|
1047
|
-
addItem(product: Product, quantity: number) {
|
|
1048
|
-
return ({ setState }) => {
|
|
1049
|
-
// Validation, calculations, side effects
|
|
1050
|
-
if (quantity <= 0) return;
|
|
1051
|
-
|
|
1052
|
-
setState((cart) => ({
|
|
1053
|
-
...cart,
|
|
1054
|
-
items: [...cart.items, { product, quantity }],
|
|
1055
|
-
total: calculateTotal(cart, product, quantity),
|
|
1056
|
-
}));
|
|
1057
|
-
};
|
|
1058
|
-
},
|
|
1059
|
-
},
|
|
1060
|
-
});
|
|
1061
|
-
|
|
1062
|
-
// ❌ Avoid - business logic in components
|
|
1063
|
-
function Component() {
|
|
1064
|
-
const [cart, setCart] = useCart();
|
|
1065
|
-
|
|
1066
|
-
const addItem = (product, quantity) => {
|
|
1067
|
-
if (quantity <= 0) return;
|
|
1068
|
-
setCart((cart) => ({
|
|
1069
|
-
...cart,
|
|
1070
|
-
items: [...cart.items, { product, quantity }],
|
|
1071
|
-
total: calculateTotal(cart, product, quantity),
|
|
1072
|
-
}));
|
|
1073
|
-
};
|
|
1074
|
-
}
|
|
1075
|
-
```
|
|
1076
|
-
|
|
1077
|
-
### 3. Use Selectors for Performance
|
|
1078
|
-
|
|
1079
|
-
```tsx
|
|
1080
|
-
// ✅ Good - only re-renders when relevant data changes
|
|
1081
|
-
const [userName] = useAuth((state) => state.user?.name);
|
|
1082
|
-
|
|
1083
|
-
// ❌ Avoid - re-renders on any auth state change
|
|
1084
|
-
const [auth] = useAuth();
|
|
1085
|
-
const userName = auth.user?.name;
|
|
1086
|
-
```
|
|
1087
|
-
|
|
1088
|
-
### 4. Leverage Selector Hooks for Reusability
|
|
1089
|
-
|
|
1090
|
-
```tsx
|
|
1091
|
-
// ✅ Good - reusable, testable
|
|
1092
|
-
const useUserName = useAuth.createSelectorHook((state) => state.user?.name);
|
|
1093
|
-
|
|
1094
|
-
// Use in multiple components
|
|
1095
|
-
function Header() {
|
|
1096
|
-
const [name] = useUserName();
|
|
1097
|
-
return <div>Hello {name}</div>;
|
|
1098
|
-
}
|
|
1099
|
-
|
|
1100
|
-
function Profile() {
|
|
1101
|
-
const [name] = useUserName();
|
|
1102
|
-
return <h1>{name}'s Profile</h1>;
|
|
1103
|
-
}
|
|
1104
|
-
```
|
|
1105
|
-
|
|
1106
|
-
### 5. Use Context for Isolated Features
|
|
1107
|
-
|
|
1108
|
-
```tsx
|
|
1109
|
-
import { createContext } from 'react-hooks-global-states/createContext';
|
|
1110
|
-
|
|
1111
|
-
// ✅ Good - wizard state isolated to flow
|
|
1112
|
-
const WizardContext = createContext({ step: 1, data: {} });
|
|
1113
|
-
|
|
1114
|
-
function CheckoutWizard() {
|
|
1115
|
-
return (
|
|
1116
|
-
<WizardContext.Provider>
|
|
1117
|
-
<WizardSteps />
|
|
1118
|
-
</WizardContext.Provider>
|
|
1119
|
-
);
|
|
1120
|
-
}
|
|
1121
|
-
|
|
1122
|
-
// ❌ Avoid - global state for temporary UI state
|
|
1123
|
-
const useWizardState = createGlobalState({ step: 1, data: {} });
|
|
1124
|
-
```
|
|
1125
|
-
|
|
1126
|
-
### 6. Use `actionsFor` for Extending Stores
|
|
1127
|
-
|
|
1128
|
-
```tsx
|
|
1129
|
-
import { actionsFor } from 'react-hooks-global-states/actionsFor';
|
|
1130
|
-
|
|
1131
|
-
// ✅ Good - extend stores with reusable action templates
|
|
1132
|
-
const withLogging = actionsFor().with({
|
|
1133
|
-
logState() {
|
|
1134
|
-
return ({ getState }) => {
|
|
1135
|
-
console.log('Current state:', getState());
|
|
1136
|
-
};
|
|
1137
|
-
},
|
|
1138
|
-
});
|
|
1139
|
-
|
|
1140
|
-
// Apply to any store!
|
|
1141
|
-
const counterWithLogging = withLogging(useCounter.api());
|
|
1142
|
-
const authWithLogging = withLogging(useAuth.api());
|
|
1143
|
-
|
|
1144
|
-
// ❌ Avoid - repeating the same actions in multiple stores
|
|
1145
|
-
const useCounter = createGlobalState(0, {
|
|
1146
|
-
actions: {
|
|
1147
|
-
logState() {
|
|
1148
|
-
/* duplicate code */
|
|
1149
|
-
},
|
|
1150
|
-
},
|
|
1151
|
-
});
|
|
1152
|
-
|
|
1153
|
-
const useAuth = createGlobalState(null, {
|
|
1154
|
-
actions: {
|
|
1155
|
-
logState() {
|
|
1156
|
-
/* duplicate code */
|
|
1157
|
-
},
|
|
1158
|
-
},
|
|
1159
|
-
});
|
|
1160
|
-
```
|
|
1161
|
-
|
|
1162
|
-
---
|
|
1163
|
-
|
|
1164
|
-
## 🔗 Resources
|
|
1165
|
-
|
|
1166
|
-
- **[Live Example](https://johnny-quesada-developer.github.io/global-hooks-example/)**
|
|
1167
|
-
- **[Video Overview](https://www.youtube.com/watch?v=1UBqXk2MH8I/)**
|
|
1168
|
-
- **[CodePen Demo](https://codepen.io/johnnynabetes/pen/WNmeGwb?editors=0010)**
|
|
1169
|
-
- **[DevTools Extension](https://chromewebstore.google.com/detail/bafojplmkpejhglhjpibpdhoblickpee)**
|
|
1170
|
-
- **[GitHub Repository](https://github.com/johnny-quesada-developer/react-hooks-global-states)**
|
|
1171
|
-
|
|
1172
|
-
## 📦 Related Packages
|
|
1173
|
-
|
|
1174
|
-
- **[react-global-state-hooks](https://www.npmjs.com/package/react-global-state-hooks)** - Web with localStorage integration
|
|
1175
|
-
- **[react-native-global-state-hooks](https://www.npmjs.com/package/react-native-global-state-hooks)** - React Native with AsyncStorage integration
|
|
1176
|
-
|
|
1177
|
-
---
|
|
1178
|
-
|
|
1179
|
-
## 📄 License
|
|
1180
|
-
|
|
1181
|
-
MIT © [Johnny Quesada](https://github.com/johnny-quesada-developer)
|
|
1182
|
-
|
|
1183
|
-
---
|
|
1184
|
-
|
|
1185
|
-
## 🙏 Contributing
|
|
1186
|
-
|
|
1187
|
-
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
1188
|
-
|
|
1189
|
-
---
|
|
1190
|
-
|
|
1191
|
-
**Made with ❤️ by developers, for developers**
|