Browse guides

Coordinate server data with QueryClient

Read and update one asynchronous todo source through shared query and mutation state.

The self-contained example reads an in-memory source, adds one item, invalidates the query after success, fetches the current list and renders both handle statuses.

Table of contents

Main APIs

QueryClient
Creates query and mutation handles over a shared cache with invalidation and persistence controls.
QueryHandle
Exposes the public state and operations of a query.
MutationHandle
Exposes the public state and operations of a mutation.
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);

Error

Query and mutation handles expose separate error states.

Exact references