pnpm i @ethang/store
import { Store } from "@ethang/store";
const store = new Store({ count: 0 });
store.set((state) => {
state.count += 1; // Immutable changes via Immer
})
store.get(); // { count: 1 }
store.get(state => state.count); // 1
const unsubscribe = store.subscribe((state) => {
console.log(`Count is now ${state.count}`);
})
unsubscribe(); // Don't forget to clean up.
const counterButton = document.querySelector('#counter');
counterButton.onclick = () => store.set(state => {
state.count += 1;
})
const bindFn = store.bind((state, element) => {
element.textContent = state.count;
})
bindFn(counterButton);
// Automatically cleans up after element is removed from DOM
- Fine-grained reactivity, does not trigger rerenders.
<button
onClick={() => {
store.set(state => {
state.count += 1;
})
}}
ref={store.bind((state, element) => {
element.textContent = state.count;
})}
/>
- Sync w/ React reconciliation, triggers component rerenders on state changes.
import { useSyncExternalStore } from "react";
const state = useSyncExternalStore(
listener => store.subcribe(listener),
() => store.get(), // get client snapshot
() => store.get(), // get server snaphot
);
<div>{state.count}</div>
- A few less rerenders.
import { useSyncExternalStoreWithSelector } from "use-sync-external-store/with-selector.js";
const count = useSyncExternalStoreWithSelector(
listener => store.subscribe(listener),
() => store.get(), // get client snapshot
() => store.get(), // get server snaphot
state => state.count,
);
<div>{count}</div>
// Subscribers aren't notified until after work in set is done
store.set(state => {
state.count += 1;
state.count += 1;
state.count += 1;
})
import { Store } from "@ethang/store";
import { useSyncExternalStoreWithSelector } from "use-sync-external-store/with-selector.js";
const initialCountStoreState = {count: 0};
const initialTextStoreState = {hello: "Hello", world: "World!"};
const countStore = new Store(initialCountStoreState);
const textStore = new Store(initialTextStoreState)
const useCountStore = <T,>(selector: (state: typeof initialTextStoreState) => T) => {
return useSyncExternalStoreWithSelector(
listener => textStore.subscribe(listener),
() => textStore.get(),
() => textStore.get(),
selector,
);
}
const incrementCount = () => {
store.set(state => {
state.count += 1;
})
}
const MyComponent = () => {
const {hello} = useCountStore(state => {
return {
hello: state.hello,
}
});
return (
<div>
<div>Count</div>
<button
onClick={incrementCount}
ref={countStore.bind((state, element) => {
element.textContent = state.count;
})}
/>
<ThirdPartyComponent text={hello} />
</div>
);
}
Use TanStack Query