Skip to content

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

bash
npm install statemesh-core
bash
pnpm add statemesh-core
bash
yarn add statemesh-core

StateMesh 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:

ts
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:

tsx
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

ts
// 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

ts
// 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:

ts
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:

ts
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 }
});
tsx
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

Released under the MIT License.