Skip to content

Mutations

Mutations are API/server writes with optimistic cache updates, rollback, invalidation, and refetch. They complement resources (which handle reads).

Define a Mutation

ts
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"
});
OptionTypeDescription
optimistic(state, input, ctx) => voidUpdate state/cache optimistically before the API call
mutate(input) => Promise<T>The async write operation
invalidateTag[]Tags to invalidate after success
refetch"active" | "all" | string[]Which invalidated resources to refetch
rollbackbooleanRollback optimistic update on failure (default: true when optimistic is set)
offlinebooleanQueue the mutation when offline and flush on reconnect

Use in React

tsx
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

FieldTypeDescription
run(input) => Promise<T>Execute the mutation
pendingbooleanTrue while the mutation is in-flight
errorError | undefinedThe last mutation error
reset() => voidClear error state

Optimistic Updates

The optimistic function runs before the API call. Use ctx.setResourceData to update the resource cache optimistically:

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

ts
{
  invalidate: [{ type: "products" }],  // Invalidate all "products" tagged resources
  refetch: "active"                     // Refetch only resources currently visible
}
refetch valueBehavior
"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:

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

ts
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

ts
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

Released under the MIT License.