createPubSubStore
A state management hook implemented on Publish-Subscribe pattern. It allows components to subscribe to state changes and receive updates whenever the state is modified, providing a scalable and decoupled state management solution.
N.B.: to work properly, objects like Set, Map, Date or more generally objects without Symbol.iterator must be treated as immutable.
Demo
INFO
In this example it has been used createPubSubStore hook to create a global store that contains a user object and a spinner value to handle logic of two components. The updateStore and usePubSubStore function returned from createPubSubStore are exported to be used from other components:
- Component 1 uses usePubSubStore hook subscribing user object from store to edit user object properties by three input tags.
- Component 2 uses:
- usePubSubStore hook subscribing only name property of user object from store to edit name value.
- updateStore hook to update spinner property from store when button handle spinner is clicked.
Show source code
import { memo } from "react";
import { createPubSubStore } from "../../../..";
//File store.ts
const store = createPubSubStore(
{
user: {
id: 0,
name: "",
eta: 0
},
spinner: false
},
{
toggleSpinner: (store, val: boolean) => {
store.spinner = val;
}
}
);
export const { usePubSubStore, mutateStore } = store;
//import {usePubSubStore} from '../store.ts';
const Comp1 = () => {
const [state, setState] = usePubSubStore(store => store.user);
return <div>
<label htmlFor="id">ID:</label>
<input type="number" name="id" value={state.id} onChange={(e) => setState(store => ({ ...store, id: Number(e.target.value) }))} />
<label htmlFor="name">NAME:</label>
<input type="text" name="name" value={state.name} onChange={(e) => setState(store => ({ ...store, name: e.target.value }))} />
<label htmlFor="eta">ETA:</label>
<input type="number" name="eta" value={state.eta} onChange={(e) => setState(store => ({ ...store, eta: Number(e.target.value) }))} />
</div>
}
//import {usePubSubStore} from '../store.ts';
const Comp2 = memo(() => {
const [state, setState, , mutators] = usePubSubStore(store => store.user.name);
return <div>
<label htmlFor="name">NAME:</label>
<input type="text" name="name" value={state} onChange={(e) => setState(e.target.value)} />
<button onClick={async () => {
mutators.toggleSpinner(true);
await new Promise(res => setTimeout(res, 4000));
mutators.toggleSpinner(false);
}}>Enable Spinner</button>
</div>
})
//import {usePubSubStore} from '../store.ts';
export const CreatePubSubStore = () => {
const [spinner] = usePubSubStore(store => store.spinner);
return <div style={{ display: "grid", gridTemplateRows: "auto auto", gap: 20, justifyContent: "center" }}>
<fieldset style={{padding: 20}}>
<legend>Component 2</legend>
<Comp2 />
</fieldset>
{
spinner
? <p>Loading...</p>
: <fieldset style={{padding: 20}}>
<legend>Component 1</legend>
<Comp1 />
</fieldset>
}
</div>
}Types
PubSubMutatorsFn
@templateT - The store object type.
The map of mutator functions accepted by createPubSubStore. Each mutator receives the current store as its first argument, followed by any number of additional custom arguments.
export type PubSubMutatorsFn<T extends object> = Record<string, (store: T, ...args: any[]) => void>;PubSubMutators
@templateE - The original mutators function map type.
The derived mutators object exposed on the store instance. Each key mirrors a key of the original mutatorsFn map, but the first store argument is stripped — callers only pass the additional custom arguments.
export type PubSubMutators<E extends PubSubMutatorsFn<any>> = Record<keyof E, (...args: ExtractTail<Parameters<E[keyof E]>>) => void>;CreatePubSubStoreProps
@templateT - The store object type. Must be a plain object.@templateE - The mutators function map type. Each value must be a function whose first argument is the current storeT.
Parameters accepted by createPubSubStore.
| Property | Type | Required | Description |
|---|---|---|---|
obj | T | ✓ | The initial state of the store. Shallow-copied on creation. If persist is set and a previously serialised value exists in storage, the persisted value takes precedence over this object. |
mutatorsFn | E | An optional map of named mutator functions used to update the store in a structured way. Each mutator receives the current store by reference as its first argument and may mutate it directly — the store is then published to all subscribers automatically. The store argument is stripped from the exposed {@link PubSubMutators} so callers only pass the remaining arguments. | |
persist | "localStorage" \| "sessionStorage" | When provided, the store is persisted to the specified Web Storage API and rehydrated on the next page load: - "localStorage" — Persists across browser sessions until explicitly cleared. - "sessionStorage" — Persists only for the duration of the current browser session. When omitted, the store lives only in memory. |
CreatePubSubStoreResult
@templateT - The store object type.@templateE - The mutators function map type.
Return value of createPubSubStore.
| Property | Type | Required | Description |
|---|---|---|---|
getStore | () => T | ✓ | Returns the current store value synchronously, outside of any React component. Useful for reading state in event handlers, utilities, or non-React code without subscribing to updates. |
mutateStore | (cb: (globStore: T) => void) => void | ✓ | Imperatively mutates the store outside of any React component. Receives the current store by reference via cb — mutate it directly and the updated value is published to all subscribers and persisted to storage if persist was configured. |
mutators | PubSubMutators<E> | ✓ | The derived mutator functions generated from mutatorsFn. Each key mirrors the original map but with the leading store argument removed, so callers only supply the additional custom arguments. Can be called outside of React components. See {@link PubSubMutators}. |
usePubSubStore | { (subscribe?: undefined): [ T, (store: T \| ((currStore: T) => T)) => void, () => T, PubSubMutators<E> ]; <C>(subscribe ?: (store: T) => C): [ C, (store: C \| ((currStore: C) => C)) => void, () => C, PubSubMutators<E> ]; } | ✓ | A React hook that subscribes a component to the store and returns a reactive tuple. Supports two overloads: - Without subscribe — The component receives and re-renders on changes to the full store object T. - With subscribe — A selector function (store: T) => C is used to derive a slice C from the store. The component only re-renders when the selected slice changes (deep equality), avoiding unnecessary re-renders for unrelated store updates. Returns a four-element tuple: | Index | Type | Description | | ----- | ---- | ----------- | | 0 | T | C | The current store value or selected slice. Reactive — triggers a re-render on change. | | 1 | (store: T \| C \| ((curr: T) => T) \| ((curr: C) => C)) => void | A setter that accepts a new value or an updater function. Updates the store (or the selected slice) and notifies all subscribers. | | 2 | () => T | () => C | A stable getter that returns the current value synchronously without causing a re-render. | | 3 | {@link PubSubMutators}<E> | The same derived mutators object as {@link CreatePubSubStoreResult.mutators}, available inside the component. | |
