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

# Protecting Against Stale Edits

> Stop a save from overwriting a change the editor never saw

Every mutation is a [serializable transaction](https://docs.bijection.com/database/advanced/occ.md). A
mutation that reads a document and then writes it can't race another mutation:
if the document changes underneath it, Bijection retries the mutation against the
new value. You don't need a version field to keep a mutation correct.

A stale edit is a different problem. It happens between mutations, while a person
looks at a form:

1. Ana opens a note.
2. Ben opens the same note, changes it and saves.
3. Ana saves the text she started from, and Ben's change is gone.

Both saves are valid transactions, so the database has nothing to object to. Only
your app knows that Ana's save was based on an old copy. If two people can edit
the same record, keep a version field and make each save say which version it
started from. If only one person edits a record, or the last save should win, you
don't need any of this.

## Add a version field

Store a counter on the document. Classify it like the table's other fields:

```ts bijection/schema.ts theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
export const notes = defineTable({
  text: v.string(),
  version: v.number(),
}).access(access.resources.note, {
  document: { read: "read", create: "create", delete: "delete" },
  fields: {
    text: { read: "read", create: "create", update: "edit" },
    version: { read: "read", create: "create", update: "edit" },
  },
});
```

Start it at `0` when you insert the document.

## Compare it in the mutation

The client sends the version it read. The mutation compares it with the stored
version, and the transaction guarantees that nothing changes between that check
and the write:

```ts bijection/notes.ts theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
import { BijectionError, v } from "bijection/values";
import { mutation } from "./_generated/server";

export const update = mutation({
  args: {
    noteId: v.id("notes"),
    expectedVersion: v.number(),
    text: v.string(),
  },
  returns: v.null(),
  handler: async (ctx, { noteId, expectedVersion, text }) => {
    const note = await ctx.db.get(noteId);
    if (!note || note.version !== expectedVersion)
      throw new BijectionError({
        code: "NoteChanged",
        message: "This note changed. Reload it before editing.",
      });
    await ctx.db.patch(noteId, { text, version: note.version + 1 });
    return null;
  },
});
```

## Handle the error in the client

Pass the version the editor opened with, not the latest one from `useQuery`.
A live query updates while the form is open, so reading the version at save time
would hide the change you want to catch:

```tsx src/EditNote.tsx theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
import { useState } from "react";
import { useMutation } from "bijection/react";
import { BijectionError } from "bijection/values";
import { api } from "../bijection/_generated/api";
import type { Doc } from "../bijection/_generated/dataModel";

export function EditNote({ note }: { note: Doc<"notes"> }) {
  const update = useMutation(api.notes.update);
  const [text, setText] = useState(note.text);
  const [expectedVersion] = useState(note.version);
  const [message, setMessage] = useState("");

  async function save() {
    try {
      await update({ noteId: note._id, expectedVersion, text });
    } catch (error) {
      if (
        error instanceof BijectionError &&
        (error.data as { code?: string }).code === "NoteChanged"
      ) {
        setMessage("Someone else changed this note. Reload it to continue.");
      } else {
        throw error;
      }
    }
  }

  return (
    <form
      onSubmit={(event) => {
        event.preventDefault();
        void save();
      }}
    >
      <input value={text} onChange={(event) => setText(event.target.value)} />
      <button type="submit">Save</button>
      <p role="status">{message}</p>
    </form>
  );
}
```

When the save fails, show the person the latest text and let them decide what to
keep. Don't retry with the new version automatically: that brings back the
overwrite this check exists to stop.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.