> ## Documentation Index
> Fetch the complete documentation index at: https://claritylabs.inc/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Get Started with CL Sync in Your React App

> Install CL Sync, define a collection and mutation, create a store, and wire everything into React in five steps.

This guide walks you through building a working todo app with CL Sync. You'll define a typed collection, write an optimistic mutation, create a scoped store, and render reactive data with hooks — all in about five minutes.

<Steps>
  ### Install the package

  CL Sync has no required peer dependencies. Install the core package:

  ```bash theme={"system"}
  npm install @claritylabs/cl-sync
  ```

  If you plan to use the React hooks, ensure you have `react >=18.0.0` already installed. For the Convex adapter, you need `convex >=1.30.0`.

  ### Define a collection

  A collection describes a named set of records. `defineCollection` is a typed identity helper — it exists purely for type inference and returns the definition unchanged.

  ```typescript theme={"system"}
  import { defineCollection, type SyncRecord } from "@claritylabs/cl-sync";

  type Todo = SyncRecord & {
    id: string;
    title: string;
    completed: boolean;
  };

  export const todoCollection = defineCollection<Todo, { listId: string }>({
    name: "todos",
    getId: (todo) => todo.id,
    deriveKey: (args) => args.listId,
    sort: (a, b) => a.title.localeCompare(b.title),
  });
  ```

  `deriveKey` turns query arguments into a stable cache key. Records for `listId: "inbox"` are stored and retrieved separately from records for `listId: "archive"`.

  ### Define a mutation

  Mutations describe how to update local state optimistically (via `reducer`) and how to persist the change to your server (via `flush`). The outbox writes the mutation to IndexedDB before calling `flush`, so it survives a page reload.

  ```typescript theme={"system"}
  import { defineMutation } from "@claritylabs/cl-sync";

  export const createTodo = defineMutation<
    { id: string; title: string; listId: string },
    { ok: true }
  >({
    name: "todos.create",
    reducer: (store, args) => {
      // Optimistic update — runs before the network call
      const current = store.getCollection(todoCollection, { listId: args.listId }) ?? [];
      void store.upsertCollection(todoCollection, { listId: args.listId }, [
        ...current,
        { id: args.id, title: args.title, completed: false },
      ]);
    },
    flush: async (args, clientMutationId) => {
      const response = await fetch("/api/todos", {
        method: "POST",
        headers: { "content-type": "application/json" },
        body: JSON.stringify({ ...args, clientMutationId }),
      });
      if (!response.ok) throw new Error(`Create failed: ${response.status}`);
      return (await response.json()) as { ok: true };
    },
  });
  ```

  <Tip>
    Pass `clientMutationId` in your request body so your server can deduplicate retried mutations — CL Sync generates a stable ID per outbox entry and reuses it on retry.
  </Tip>

  ### Create the store

  `createSyncStore` is the entry point for all sync state. Scope your store to the current user so that data never leaks across sessions.

  ```typescript theme={"system"}
  import { createSyncStore } from "@claritylabs/cl-sync";

  const store = createSyncStore({
    scope: {
      appId: "todo-app",
      environment: import.meta.env.MODE,
      userId: currentUser.id,
    },
    schema: {
      version: 1,
      collections: [
        { name: "todos", version: 1, fields: ["id", "title", "completed"] },
      ],
    },
    mutations: [createTodo],
  });
  ```

  <Note>
    Create the store once and reuse the same instance across your component tree. Re-creating the store on every render resets all sync state.
  </Note>

  ### Wrap with SyncProvider and use hooks

  `SyncProvider` mounts the store into React context, calls `store.hydrate()` on mount, and (with `flushOnHydrate`) replays any mutations that were pending when the user last closed the tab.

  ```tsx theme={"system"}
  import {
    SyncProvider,
    useSyncCollection,
    useSyncMutation,
    useSyncStatus,
  } from "@claritylabs/cl-sync/react";

  export function App() {
    return (
      <SyncProvider store={store} mutations={[createTodo]} flushOnHydrate>
        <TodoList />
      </SyncProvider>
    );
  }

  function TodoList() {
    const todos = useSyncCollection(todoCollection, { listId: "inbox" }) ?? [];
    const create = useSyncMutation(createTodo);
    const status = useSyncStatus();

    return (
      <button
        disabled={!status.hydrated}
        onClick={() =>
          create({
            id: crypto.randomUUID(),
            listId: "inbox",
            title: "Ship it",
          })
        }
      >
        Add todo ({todos.length} todos loaded)
      </button>
    );
  }
  ```

  `useSyncCollection` returns `undefined` until the store hydrates, so the `?? []` fallback prevents rendering an empty list during the brief boot window. Once `status.hydrated` is `true`, the IndexedDB cache has loaded and any pending mutations have been replayed.
</Steps>

## What's next

<CardGroup cols={2}>
  <Card title="Core Concepts" icon="book" href="/docs/cl-sync/concepts">
    Dive deeper into scopes, the outbox, and optimistic updates.
  </Card>

  <Card title="Collections" icon="table" href="/docs/cl-sync/collections">
    Learn about persistence, redaction, and derived cache keys.
  </Card>

  <Card title="Mutations" icon="pen" href="/docs/cl-sync/mutations">
    Explore the full mutation lifecycle, flush options, and rollback patterns.
  </Card>

  <Card title="Convex Adapter" icon="plug" href="/docs/cl-sync/convex-adapter">
    Replace the manual `flush` function with a Convex mutation reference.
  </Card>
</CardGroup>
