Mutations
Mutations are API/server writes with optimistic cache updates, rollback, invalidation, and refetch. They complement resources (which handle reads).
Define a Mutation
const createProduct = mesh.mutation("products.create", {
optimistic(_state, input: { name: string }, ctx) {
ctx.setResourceData(productsResource, { search: "", page: 1 }, (current) => [
{ id: "temp", name: input.name, optimistic: true },
...(current ?? [])
]);
},
async mutate(input) {
return api.post<Product>("/products", input);
},
invalidate: [{ type: "products" }],
refetch: "active"
});| Option | Type | Description |
|---|---|---|
optimistic | (state, input, ctx) => void | Update state/cache optimistically before the API call |
mutate | (input) => Promise<T> | The async write operation |
invalidate | Tag[] | Tags to invalidate after success |
refetch | "active" | "all" | string[] | Which invalidated resources to refetch |
rollback | boolean | Rollback optimistic update on failure (default: true when optimistic is set) |
offline | boolean | Queue the mutation when offline and flush on reconnect |
Use in React
import { useMeshMutation } from "statemesh-core";
function NewProductButton() {
const create = useMeshMutation(createProduct);
return (
<button
disabled={create.pending}
onClick={() => void create.run({ name: "Keyboard" })}
>
{create.pending ? "Creating..." : "Create Product"}
</button>
);
}useMeshMutation Return
| Field | Type | Description |
|---|---|---|
run | (input) => Promise<T> | Execute the mutation |
pending | boolean | True while the mutation is in-flight |
error | Error | undefined | The last mutation error |
reset | () => void | Clear error state |
Optimistic Updates
The optimistic function runs before the API call. Use ctx.setResourceData to update the resource cache optimistically:
optimistic(state, input, ctx) {
ctx.setResourceData(productsResource, { search: "", page: 1 }, (current) => [
{ id: "temp-id", name: input.name, optimistic: true },
...(current ?? [])
]);
}If the mutation fails, the optimistic update is rolled back automatically.
Invalidation and Refetch
After a successful mutation, invalidate related resources:
{
invalidate: [{ type: "products" }], // Invalidate all "products" tagged resources
refetch: "active" // Refetch only resources currently visible
}refetch value | Behavior |
|---|---|
"active" | Refetch only resources with active subscribers |
"all" | Refetch all invalidated resources |
["products.list"] | Refetch specific resources by name |
Offline Queueing
Queue mutations when the browser is offline:
const saveDraft = mesh.mutation("draft.save", {
offline: true,
async mutate(input: DraftInput) {
return api.post<Draft>("/drafts", input);
}
});
// Persist the offline queue
mesh.persistQueuedMutations({
key: "app:mutation-queue",
storage: "localStorage",
ttl: "1d"
});
// Flush queued mutations when back online
await mesh.runQueuedMutations();Entity Helpers
For list/detail cache sync:
const normalized = mesh.normalizeEntities(products, (p) => p.id);
const merged = mesh.mergeEntities(normalized, [updatedProduct], (p) => p.id);
const list = mesh.denormalizeEntities(merged);Example: Update Product
const updateProduct = mesh.mutation("products.update", {
optimistic(state, input: { id: string; data: Partial<Product> }, ctx) {
ctx.setResourceData(productsResource, {}, (current) =>
(current ?? []).map((p) =>
p.id === input.id ? { ...p, ...input.data, optimistic: true } : p
)
);
},
async mutate(input) {
return api.put<Product>(`/products/${input.id}`, input.data);
},
invalidate: [{ type: "products" }],
refetch: "active"
});Important Notes
TIP
Mutations and resources work together. Mutations write data and invalidate resource tags; resources refetch and update the cache.
WARNING
If optimistic is defined but rollback is not explicitly set, rollback defaults to true. Set rollback: false explicitly if you don't want automatic rollback.
TIP
The offline queue persists across page reloads when used with persistQueuedMutations. Queued mutations flush automatically when the browser reconnects.
Next Steps
- Resources — Cached API reads
- API Client — HTTP client with auth and retry
- Persistence — State and cache persistence
