Browse recipes

Coordinate a query and a mutation

Coordinate one todo read and write through shared server-data state.

Table of contents

APIs used

mount
Starts a Valyrian.js application on a DOM target.
QueryClient
Creates query and mutation handles over a shared cache with invalidation and persistence controls.

Run this recipe

Starting project
Use the local TSX and ESM project from Stage 1 with Valyrian.js installed and its ESM browser build configured.
File
Replace src/client-entry.tsx with this recipe snippet.
Run
Run npm run build, serve public with npx --yes serve@14.2.5 public --listen 8000, open http://localhost:8000 and use the example's visible controls.
Observable result
The mutation invalidates the query, the second fetch reads both items and the view reports separate query and mutation statuses. The cache remains available until Clear query cache is selected.

Starting point

Create a QueryClient over the asynchronous in-memory source included in the snippet.

Steps

Call query(config) for the list, fetch() to read it, mutation(config) for the write and execute(payload) to add one item. Invalidate the query after success and fetch the current list.

tsx
import { mount } from "valyrian.js";
import { QueryClient } from "valyrian.js/query";

type Todo = { id: number; title: string };
const records: Todo[] = [{ id: 1, title: "Learn Valyrian.js" }];
let nextId = 2;

const client = new QueryClient({ staleTime: 30_000 });
const todos = client.query({
  key: ["todos"],
  fetcher: async (): Promise<Todo[]> => records.map((todo) => ({ ...todo })),
});
const addTodo = client.mutation<{ title: string }, Todo>({
  execute: async (todo) => {
    const created = { id: nextId, title: todo.title };
    nextId += 1;
    records.push(created);
    return { ...created };
  },
  onSuccess: () => todos.invalidate(),
});

await todos.fetch();
await addTodo.execute({ title: "Learn Valyrian.js" });
await todos.fetch();

let cacheStatus = `cached todos: ${todos.state.data?.length ?? 0}`;

function QueryStatus() {
  return (
    <main>
      <p>
        query: {todos.state.status}; mutation: {addTodo.state.status}
      </p>
      <p>{cacheStatus}</p>
      <button
        type="button"
        onclick={() => {
          client.clear();
          cacheStatus = "cache cleared";
        }}
      >
        Clear query cache
      </button>
    </main>
  );
}

mount("body", QueryStatus);

Result

The mutation invalidates the query, the second fetch reads both items and the view reports separate query and mutation statuses. The cache remains available until Clear query cache is selected.

Limits

The query and mutation handles retain separate error states. Clear query cache calls client.clear() as an explicit user action.

Continue