@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
JavaScript
// 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
};