@plasius/react-state
Version:
Tiny, testable, typesafe React Scoped Store helper.
224 lines (217 loc) • 6.67 kB
JavaScript
;
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