UNPKG

@plasius/react-state

Version:

Tiny, testable, typesafe React Scoped Store helper.

224 lines (217 loc) 6.67 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var index_exports = {}; __export(index_exports, { MetadataStore: () => MetadataStore, StoreProvider: () => StoreProvider, __noop: () => __noop, createScopedStoreContext: () => createScopedStoreContext, createStore: () => createStore, useDispatch: () => useDispatch, useStore: () => useStore }); module.exports = __toCommonJS(index_exports); // src/types.ts var __noop = null; // src/create-scoped-store.tsx var import_react = require("react"); // src/store.ts function createStore(reducer, initialState) { let state = initialState; const listeners = /* @__PURE__ */ new Set(); const keyListeners = /* @__PURE__ */ new Map(); const selectorListeners = /* @__PURE__ */ new Set(); const getState = () => state; const dispatch = (action) => { const prevState = state; const nextState = reducer(state, action); if (Object.is(prevState, nextState)) { state = nextState; return; } state = nextState; for (const listener of [...listeners]) listener(); for (const [key, set] of keyListeners.entries()) { if (!Object.is(prevState[key], state[key])) { for (const listener of [...set]) listener(state[key]); } } selectorListeners.forEach((entry) => { const nextValue = entry.selector(state); if (!Object.is(entry.lastValue, nextValue)) { entry.lastValue = nextValue; entry.listener(nextValue); } }); }; const subscribe = (listener) => { listeners.add(listener); return () => { listeners.delete(listener); }; }; const subscribeToKey = (key, listener) => { const set = keyListeners.get(key) ?? /* @__PURE__ */ new Set(); set.add(listener); keyListeners.set(key, set); return () => { set.delete(listener); if (set.size === 0) keyListeners.delete(key); }; }; const subscribeWithSelector = (selector, listener) => { const entry = { selector, listener, lastValue: selector(state) }; selectorListeners.add(entry); return () => { selectorListeners.delete(entry); }; }; return { getState, dispatch, subscribe, subscribeToKey, subscribeWithSelector }; } // src/create-scoped-store.tsx var import_jsx_runtime = require("react/jsx-runtime"); function shallowEqual(a, b) { if (Object.is(a, b)) return true; if (typeof a !== "object" || a === null || typeof b !== "object" || b === null) return false; const ak = Object.keys(a), bk = Object.keys(b); if (ak.length !== bk.length) return false; for (let i = 0; i < ak.length; i++) { const k = ak[i]; if (!Object.prototype.hasOwnProperty.call(b, k) || !Object.is(a[k], b[k])) return false; } return true; } function createScopedStoreContext(reducer, initialState) { const Context = (0, import_react.createContext)(null); const store = createStore(reducer, initialState); const Provider = ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Context.Provider, { value: store, children }); const useStore2 = () => { const ctx = (0, import_react.useContext)(Context); if (!ctx) throw new Error("Store not found in context"); return (0, import_react.useSyncExternalStore)(ctx.subscribe, ctx.getState, ctx.getState); }; const useDispatch2 = () => { const ctx = (0, import_react.useContext)(Context); if (!ctx) throw new Error("Dispatch not found in context"); return (action) => ctx.dispatch(action); }; function useSelector(selector, isEqual = shallowEqual) { const ctx = (0, import_react.useContext)(Context); if (!ctx) throw new Error("Store not found in context"); const state = (0, import_react.useSyncExternalStore)( ctx.subscribe, ctx.getState, ctx.getState ); const lastRef = (0, import_react.useRef)(null); const last = lastRef.current; const nextSelected = selector(state); if (last && last.state === state && isEqual(last.selected, nextSelected)) { return last.selected; } lastRef.current = { state, selected: nextSelected }; return nextSelected; } return { store, Context, Provider, useStore: useStore2, useDispatch: useDispatch2, useSelector }; } // src/provider.tsx var import_react2 = require("react"); var import_jsx_runtime2 = require("react/jsx-runtime"); var StoreContext = (0, import_react2.createContext)(void 0); function useStoreInstance() { const store = (0, import_react2.useContext)(StoreContext); if (!store) { throw new Error( "StoreProvider is missing in the React tree. Wrap your app with <StoreProvider store={...}>." ); } return store; } function StoreProvider({ store, children }) { return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(StoreContext.Provider, { value: store, children }); } function useStore() { const store = useStoreInstance(); const [state, setState] = (0, import_react2.useState)(() => store.getState()); (0, import_react2.useEffect)(() => { const unsubscribe = store.subscribe(() => { setState(store.getState()); }); return unsubscribe; }, [store]); return state; } function useDispatch() { const store = useStoreInstance(); return store.dispatch; } // src/metadata-store.ts var MetadataStore = class { symbol; constructor(description) { this.symbol = Symbol(description); } set(target, meta) { Object.defineProperty(target, this.symbol, { value: meta, writable: false, enumerable: false }); } get(target) { return target[this.symbol]; } has(target) { return this.symbol in target; } }; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { MetadataStore, StoreProvider, __noop, createScopedStoreContext, createStore, useDispatch, useStore }); //# sourceMappingURL=index.cjs.map