Lifecycle and Validation
Control store initialization, cleanup, and data integrity.
Lifecycle Hooks
Run code when Providers mount/unmount - load persisted data, set up listeners, sync with external systems.
const AppStore = createStore<AppStoreType>(
{ user: { name: "", email: "" }, count: 0, theme: "light" },
{
lifecycleHooks: {
storeWillMount: (store, setStore, subscribe, unsubscribe) => {
// Sync init (SPA only) - return cleanup if needed
},
storeDidMount: (store, setStore, subscribe, unsubscribe) => {
// Async init (SSR-safe) - return cleanup if needed
},
storeWillUnmount: (store) => {
// Sync cleanup (useLayoutEffect)
},
storeWillUnmountAsync: (store) => {
// Async cleanup (useEffect)
},
},
}
);
storeWillMount
Runs synchronously during render, before mount. Use for SPA client-only initialization.
storeWillMount: (store, setStore, subscribe) => {
const saved = localStorage.getItem("theme");
if (saved) setStore({ theme: saved });
const unsubscribe = subscribe("count", (count) => console.log(count));
return unsubscribe; // Cleanup for Strict Mode
}
storeDidMount
Runs asynchronously after mount. Use for SSR apps to avoid hydration errors.
storeDidMount: (store, setStore) => {
setStore({ windowWidth: window.innerWidth });
const handleResize = () => setStore({ windowWidth: window.innerWidth });
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}
storeWillUnmount / storeWillUnmountAsync
storeWillUnmount: (store) => {
localStorage.setItem("lastCount", String(store.count)); // Sync
}
storeWillUnmountAsync: (store) => {
fetch("/api/sync", { method: "POST", body: JSON.stringify(store) }); // Async
}
Execution Order
Mount: storeWillMount (sync) → storeDidMount (async)
Unmount: storeWillUnmount (sync) → cleanups → storeWillUnmountAsync (async)
Store Validation
Validate store data before it's applied.
const AppStore = createStore(
{ count: 0 },
{
validate: (data) => {
if ("count" in data && data.count < 0) return false;
return true;
},
}
);
- Truthy → Update applied
- Falsy → Update rejected
- Invalid initial data throws error
- Invalid updates silently rejected
With Zod
import { z } from "zod";
const schema = z.object({
user: z.object({ name: z.string().min(1), email: z.string().email() }),
count: z.number().int().min(0),
});
const AppStore = createStore(
{ user: { name: "John", email: "john@example.com" }, count: 0 },
{
validate: (data) => {
const partialSchema = schema.pick(
Object.fromEntries(Object.keys(data).map((k) => [k, true]))
);
const result = partialSchema.safeParse(data);
return result.success ? result.data : false;
},
}
);
Provider-Level Configuration
Provider options completely override createStore options.
<AppStore
options={{
lifecycleHooks: {
storeDidMount: (store, setStore) => {
// Provider-specific initialization
},
},
validate: (data) => true,
}}
>
<YourComponents />
</AppStore>
With React Props
Provider-level hooks can access React props:
function App({ userId }: { userId: string }) {
return (
<AppStore
options={{
lifecycleHooks: {
storeDidMount: (store, setStore) => {
fetchUserData(userId).then((data) => setStore({ user: data }));
},
},
}}
>
<YourComponents />
</AppStore>
);
}
Disable Features
<AppStore options={{ validate: undefined }}>
<AppStore options={{ lifecycleHooks: {} }}>