koshin
Version:
Fine-grained reactive store for React using useSyncExternalStore
49 lines (48 loc) • 1.53 kB
JavaScript
import { useSyncExternalStore } from "react";
class Koshin {
data;
changeListeners = new Set();
keySubscribers = {};
constructor(data) {
this.data = data;
}
get(key) {
return this.data[key];
}
set(key, value) {
const oldValue = this.data[key];
this.data[key] = value;
this.changeListeners.forEach((cb) => cb(key, value, oldValue));
this.keySubscribers[key]?.forEach((cb) => cb(value));
}
onChange(callback) {
this.changeListeners.add(callback);
return () => this.changeListeners.delete(callback);
}
subscribe(key, callback) {
if (!this.keySubscribers[key]) {
this.keySubscribers[key] = new Set();
}
this.keySubscribers[key].add(callback);
return () => this.keySubscribers[key].delete(callback);
}
}
export function createStore(obj) {
const store = new Koshin(obj);
const proxy = {};
for (const key of Object.keys(obj)) {
Object.defineProperty(proxy, key, {
get: () => store.get(key),
enumerable: true,
});
}
proxy.set = ((key, value) => store.set(key, value));
proxy.onChange = ((cb) => store.onChange(cb));
// ✅ signal hook for fine-grained reactive values
proxy.signal = ((key) => {
const value = useSyncExternalStore((cb) => store.subscribe(key, cb), () => store.get(key));
const setValue = (v) => store.set(key, v);
return [value, setValue];
});
return proxy;
}