Edit title
Double-click a todo, type a new title, press Enter. The old title comes back. Let's save it, with one request that updates every list at once.
Editable copies
Records are read-only, so when you edit a todo, you're editing a copy of it. The starter's list hands each item an editable copy. In app/components/todo-app/todo-list.gts, each todo passes through checkout:
import { checkout } from '@warp-drive/core/reactive';
export class TodoList extends Component<Signature> {
<template>
{{#each @todos as |immutableTodo|}}
<Await @promise={{this.checkout immutableTodo}}>
<:success as |mutableTodoCopy|>
<TodoItem @todo={{mutableTodoCopy}} ... />
</:success>
</Await>
{{/each}}
</template>
checkout(todo: Todo): Promise<EditableTodo> {
return checkout<EditableTodo>(todo);
}
}checkout, from @warp-drive/core/reactive, returns an editable copy of a record. The component's this.checkout is a one-line wrapper that tells TypeScript the copy is an EditableTodo, a type from app/data/schemas/todo.ts.
checkout is async, so <Await> renders the item once the copy is ready. Changes to a copy stay on it until you save, while the cache and every other view of the todo keep showing what the server last sent. The title form doesn't change the copy at all: it reads the new title from its input and sends only that.
Write the builder
Create a new file, app/data/builders/update.ts:
import { recordIdentifierFor } from '@warp-drive/core';
import type { ReactiveDataDocument } from '@warp-drive/core/reactive';
import { withReactiveResponse } from '@warp-drive/core/request';
import type { RequestInfo } from '@warp-drive/core/types/request';
import { buildBaseURL } from '@warp-drive/utilities';
import type { Todo, TodoAttributes } from '../schemas/todo.ts';
/** PATCH /api/todo/:id */
export function patchTodo(todo: Todo, attributes: Partial<TodoAttributes>): RequestInfo<ReactiveDataDocument<Todo>> {
return withReactiveResponse<Todo>({
method: 'PATCH',
url: buildBaseURL({ op: 'updateRecord', identifier: { type: 'todo', id: todo.id } }),
body: JSON.stringify({ data: { type: 'todo', id: todo.id, attributes } }),
// 'updateRecord' plus the todo's key: on success the cache commits the
// response to this todo, and every list holding it re-renders.
op: 'updateRecord',
records: [recordIdentifierFor(todo)],
});
}recordIdentifierFor(todo), from @warp-drive/core, returns the todo's cache key: its type and id. op: 'updateRecord' with that key in records tells the cache which todo this request saves, so the response is written into that todo. buildBaseURL turns the type and id into /api/todo/<id>, and attributes is Partial, so the body carries only what changed.
Send it
In app/components/todo-app/todo-item.gts, TitleForm has a patchTodoTitle method that doesn't save anything yet. Have it send the request:
// At the top of the file:
import { patchTodo } from '#app/data/builders/update.ts';
// ...
await this.store.request(patchTodo(todo, { title }));Check it
Double-click a todo, change its title, press Enter. It sticks, and it's still there after a reload.
The Network panel shows one PATCH and no GET. The response updated the one cached todo, and every list holds that same todo, so every list shows the new title. Without a shared cache, you'd update each list yourself, or refetch them all.
What's next
Chapter 6 saves a todo's completed state. It's the same patchTodo, but this time the todo has to change lists, and the cache can't work that out on its own.