UNPKG

@ga-ut/store-react

Version:

React adapter for @ga-ut/store-core. Fine-grained re-renders via a dependency-tracking useStore hook.

82 lines (81 loc) 2.17 kB
// src/index.ts import { useLayoutEffect, useMemo, useRef, useState } from "react"; function useStore(store) { const [, setTick] = useState({}); const render = useMemo(() => () => setTick({}), []); const depsRef = useRef(/* @__PURE__ */ new Set()); const collectingRef = useRef(false); const unsubRef = useRef(null); useLayoutEffect(() => { const hasDeps = depsRef.current.size > 0; if (!hasDeps) { if (unsubRef.current) { unsubRef.current(); unsubRef.current = null; } return; } if (!unsubRef.current) { const unsubscribe = store.subscribe((modified) => { const deps = depsRef.current; for (const k of modified) { if (deps.has(k)) { render(); break; } } }); unsubRef.current = unsubscribe; } }, [store, render]); depsRef.current.clear(); collectingRef.current = true; useLayoutEffect(() => { collectingRef.current = false; }); const proxy = useMemo(() => { const base = store.getState(); const raw = store.getRawState(); const boundCache = /* @__PURE__ */ new Map(); const handler = { get(_t, prop) { const value = base[prop]; if (collectingRef.current) { if (typeof prop === "string" || typeof prop === "symbol") { if (typeof value !== "function") { depsRef.current.add(prop); } } } if (typeof value === "function") { const cached = boundCache.get(prop); if (cached) return cached; const fn = raw[prop]; const bound = fn.bind(proxyRef.current); boundCache.set(prop, bound); return bound; } return value; }, set(_t, prop, val) { return Reflect.set(base, prop, val); } }; return new Proxy(base, handler); }, [store]); const proxyRef = useRef(proxy); proxyRef.current = proxy; useLayoutEffect( () => () => { if (unsubRef.current) { unsubRef.current(); unsubRef.current = null; } }, [] ); return proxy; } export { useStore };