Skip to content

Instantly share code, notes, and snippets.

@markmals
Created September 3, 2025 01:51
Show Gist options
  • Save markmals/f2d3965e6244469fa88d542e897e1649 to your computer and use it in GitHub Desktop.
Save markmals/f2d3965e6244469fa88d542e897e1649 to your computer and use it in GitHub Desktop.
Microstore using the same API as React's upcoming store API: https://github.com/facebook/react/pull/33215
import { createStore, useStore } from "./store";
type CountStore = { count: number; doubled: number };
const counter = createStore<CountStore, number>(
{ count: 0, doubled: 0 },
(_prev, next) => ({
count: next,
doubled: next * 2,
}),
);
export function Counter() {
const store = useStore(counter);
const increment = () => counter.update(store.count + 1);
return (
<div>
<div>
{store.count} × 2 = {store.doubled}
</div>
<button type="button" onClick={increment}>
Increment
</button>
</div>
);
}
type CountersAction = "increment" | "decrement";
const counters = createStore<number[], CountersAction>(
[1, 2, 3, 4, 5],
(prev, action) => {
switch (action) {
case "increment": {
const next = prev[prev.length - 1] ? prev[prev.length - 1] + 1 : 1;
return [...prev, next];
}
case "decrement": {
return prev.slice(0, -1);
}
}
},
);
export function Counters() {
const countArray = useStore(counters);
const increment = () => counters.update("increment");
const decrement = () => counters.update("decrement");
return (
<>
<h1>Counters</h1>
{countArray ? (
countArray.map((counter) => <Counter key={counter} />)
) : (
<i>No Counters</i>
)}
<button type="button" onClick={increment}>
Add More Counters
</button>
<button type="button" onClick={decrement}>
Remove Counters
</button>
</>
);
}
import { useCallback, useMemo, useSyncExternalStore } from "react";
const MICRO_STORE = Symbol("MICRO_STORE");
class MicroStore<Value, Action> extends EventTarget {
#value: Value;
#reducer?: (previousValue: Value, action: Action) => Value;
constructor(
initialValue: Value,
reducer?: (previousValue: Value, action: Action) => Value,
) {
super();
this.#value = initialValue;
this.#reducer = reducer;
}
get value(): Value {
return this.#value;
}
update(action: Action) {
const newValue = this.#reducer
? this.#reducer(this.#value, action)
: (action as unknown as Value);
if (newValue !== this.#value) {
this.#value = newValue;
this.dispatchEvent(new CustomEvent("update", { detail: this.#value }));
}
}
}
/**
* The return value of `createStore`.
*/
// biome-ignore lint/correctness/noUnusedVariables: Type-only
export interface Store<out Value, in Action> {
// private brand because only values from `createStore` are useable not
// arbitrary objects matching the shape.
[MICRO_STORE]: never;
update: (action: Action) => void;
}
type InternalStore<Value, Action> = Store<Value, Action> & {
_subscribe(callback: () => void): () => void;
_getSnapshot(): Value;
};
export function createStore<Value>(initialValue: Value): Store<Value, Value>;
export function createStore<Value>(
initialValue: Value,
reducer: (previousValue: Value) => Value,
): Store<Value, void>;
export function createStore<Value, Action>(
initialValue: Value,
reducer?: (previousValue: Value, action: Action) => Value,
): Store<Value, Action>;
export function createStore<Value, Action>(
initialValue: Value,
reducer?: (previousValue: Value, action: Action) => Value,
): Store<Value, Action> {
const microStore = new MicroStore(initialValue, reducer);
const store: InternalStore<Value, Action> = {
[MICRO_STORE]: undefined as never,
update(action: Action) {
microStore.update(action);
},
_subscribe(callback) {
const handleUpdate = () => callback();
microStore.addEventListener("update", handleUpdate);
return () => microStore.removeEventListener("update", handleUpdate);
},
_getSnapshot: () => microStore.value,
};
return store;
}
export function useStore<Value, Action>(store: Store<Value, Action>): Value {
const internalStore = useMemo(
() => store as InternalStore<Value, Action>,
[store],
);
const getSnapshot = useCallback(
() => internalStore._getSnapshot(),
[internalStore],
);
const subscribe = useCallback(
(callback: () => void) => internalStore._subscribe(callback),
[internalStore],
);
return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment