Getting Started
StateMesh is a TypeScript-first, transaction-first state orchestration library for React. This guide walks you through installation, creating a mesh, and connecting it to your React app.
Installation
npm install statemesh-corepnpm add statemesh-coreyarn add statemesh-coreStateMesh requires React 18 or later. It has zero runtime dependencies — React is a peer dependency.
Create a Mesh
The mesh is your application's state container. Create it with createMesh:
import { createMesh } from "statemesh-core";
const mesh = createMesh({
name: "my-app",
state: {
theme: "light" as "light" | "dark",
user: null as User | null,
cart: {
items: [] as CartItem[],
status: "idle" as "idle" | "loading" | "error"
}
}
});The state object defines your initial state shape. TypeScript infers the full state type from this object — no need to declare a separate interface.
Connect to React
Wrap your app with StateMeshProvider and use hooks to read/write state:
import { StateMeshProvider, useMeshState } from "statemesh-core";
function ThemeToggle() {
const [theme, setTheme] = useMeshState<"light" | "dark">("theme");
return (
<button onClick={() => setTheme(theme === "light" ? "dark" : "light")}>
Current: {theme}
</button>
);
}
export function App() {
return (
<StateMeshProvider mesh={mesh}>
<ThemeToggle />
</StateMeshProvider>
);
}The provider passes the mesh through React context. State reads use useSyncExternalStore, so updating theme does not rerender components that don't read it.
Read and Write State
// Read the full state
const state = mesh.getState();
// Write with a new object
mesh.setState({ ...state, theme: "dark" });
// Write a single path
mesh.setPath("theme", "dark");
mesh.setPath("cart.status", "loading");
// Reset to initial state
mesh.reset();setPath accepts dot-separated paths: "cart.items", "user.address.city", etc.
Subscribe to Changes
// Subscribe to a specific path
const unsubscribe = mesh.subscribe(
(state) => state.cart.items.length,
(count) => console.log("Cart items:", count)
);
// Later: unsubscribe();Subscriptions are path-scoped and equality-checked. The callback only fires when the selected value actually changes.
Define Actions
Actions are named, synchronous state changes:
const addItem = mesh.action(
"cart.addItem",
(state, product: Product) => {
state.cart.items.push({ ...product, quantity: 1 });
}
);
// Call it
addItem({ id: "1", name: "Keyboard", price: 99 });Handlers receive a draft copy — you can mutate it directly without affecting the original state.
Add Transactions
Transactions own the full async lifecycle:
const checkout = mesh.transaction("cart.checkout", {
optimistic(state) {
state.cart.status = "loading";
},
async effect(state, payload, ctx) {
const res = await fetch("/api/checkout", { signal: ctx.signal });
if (!res.ok) throw new Error("Checkout failed");
return res.json();
},
commit(state, order) {
state.order = order;
state.cart.items = [];
state.cart.status = "idle";
},
rollback: true,
retry: { attempts: 2, delay: 1000 }
});function CheckoutButton() {
const checkoutTx = useMeshTransaction(checkout);
return (
<button disabled={checkoutTx.pending} onClick={() => checkoutTx.run()}>
{checkoutTx.pending ? "Processing..." : "Checkout"}
</button>
);
}Next Steps
- Core Concepts — Understand the mental model behind StateMesh
- State — Deep dive into state reads, writes, and subscriptions
- Actions — Named mutations with error handling
- Transactions — Async operations with optimistic UI and rollback
- Resources — API caching, deduplication, and invalidation
