Mix stores
mixStores merges two (or more) setup objects into one, ready for createStore. Think of it as inheritance for stores: you write a generic, reusable base setup — a sort of abstract class that doesn't know the concrete properties — and mix it with a setup that implements the real ones.
To make it concrete, let's rebuild the Persist example this way: a generic "persistent store" setup, mixed with the actual note store.
:::info full library required
mixStores and the onStateChange hook ship with the installed package — they're not in the copy-paste juice file.
:::
import { createStore, useStore, mixStores, Store, StoreSetup } from "@priolo/jon"
const STORAGE_KEY = "my-app-state"
// generic setup: makes ANY setup it's mixed with persistent.
// It doesn't know which properties it's saving — like an abstract class.
function persistSetup(key: string): StoreSetup<any> {
return {
// at boot: read back the last saved state (or nothing, if there is none)
state: () => {
const saved = localStorage.getItem(key)
return saved ? JSON.parse(saved) : {}
},
// on every change: save
onStateChange: (store) => {
localStorage.setItem(key, JSON.stringify(store.state))
},
}
}
// concrete setup: the real properties and their logic
const noteSetup = {
state: {
text: "",
count: 0,
},
actions: {
reset: (_: void, store: Store) => {
localStorage.removeItem(STORAGE_KEY)
store.setText("")
store.setCount(0)
},
},
mutators: {
setText: (text: string) => ({ text }),
setCount: (count: number) => ({ count }),
},
}
// the mix: order matters! On conflicts the LAST setup wins (like a subclass
// override): the state saved by persistSetup must win over noteSetup's defaults.
const noteStore: Store = createStore(mixStores(noteSetup, persistSetup(STORAGE_KEY))!)
export default function App() {
const { text, count } = useStore(noteStore)
return (
<div>
<input value={text} onChange={e => noteStore.setText(e.target.value)} />
<button onClick={() => noteStore.setCount(count + 1)}>+1</button>
<button onClick={() => noteStore.reset()}>reset</button>
<p>text: {text} · count: {count}</p>
</div>
)
}
What's going on
mixStores(a, b, ...)mergesstate,getters,actions,mutatorsand the lifecycle hooks of every setup. If both objects are plain state they're spread together; if either is a factory, the mix becomes a factory too.- Later setups override earlier ones, exactly like a subclass overriding a method. That's why
persistSetupgoes last: its state factory (the saved state) must win overnoteSetup's defaults, and itsonStateChangemust be the one that runs. persistSetup(key)is a plain function returning a setup — so the "base class" is parameterizable and reusable: any store in the app becomes persistent by mixing it in with its own storage key.- One trade-off: a mixed setup is heterogeneous, so the result is the permissive
Storehandle rather than a precisely-inferred type (same as sibling calls inside actions). - Same trick works for splitting one big store across files, sharing a common
loading/errorblock between stores, or overriding a single action of an existing setup.
Full source: src/examples/mixStores
Live demo: open on CodeSandbox