Implement the document model reducers
📦 Reference Code: step-3-implement-reducer-operation-handlers
This step focuses on implementing the reducer logic for add, update, and delete operations.
📖 How to use this tutorial
Compare your reducer implementation​
After implementing your reducers:
# Compare your reducers with the reference
git diff tutorial/step-3-implement-reducer-operation-handlers -- document-models/todo-list/src/reducers/
# View the reference reducer implementation
git show tutorial/step-3-implement-reducer-operation-handlers:document-models/todo-list/src/reducers/todos.ts
Visual comparison with GitHub Desktop​
After committing your work, compare visually:
- Branch menu → "Compare to Branch..."
- Select
tutorial/step-3-implement-reducer-operation-handlers - Review differences in the visual interface
If you get stuck​
View or reset to a specific step:
# View the reducer code
git show tutorial/step-3-implement-reducer-operation-handlers:document-models/todo-list/src/reducers/todos.ts
# Reset to this step (WARNING: loses your changes)
git reset --hard tutorial/step-3-implement-reducer-operation-handlers
In this section, we will implement the operation reducers for the To-do List document model. In the previous step Vetra imported our document specification and scaffolded our directory through code generation. If not, you can revisit the Define TodoList Document Model section.
Understanding reducers in document models​
Reducers are a core concept in Powerhouse document models. They implement the state transition logic for each operation defined in your schema.
Connection to schema definition language (SDL): The reducers directly implement the operations you defined in your SDL. Remember how we defined AddTodoItemInput, UpdateTodoItemInput, and DeleteTodoItemInput in our schema?
The reducers provide the actual implementation of what happens when those operations are performed.
Explore the generated reducer file​
Navigate to /document-models/todo-list/src/reducers/todos.ts and open it. You should see scaffolding code that needs to be filled for the three operations you specified:
import type { TodoListTodosOperations } from "todo-tutorial/document-models/todo-list";
export const todoListTodosOperations: TodoListTodosOperations = {
addTodoItemOperation(state, action) {
// TODO: Implement "addTodoItemOperation" reducer
throw new Error('Reducer "addTodoItemOperation" not yet implemented');
},
updateTodoItemOperation(state, action) {
// TODO: Implement "updateTodoItemOperation" reducer
throw new Error('Reducer "updateTodoItemOperation" not yet implemented');
},
deleteTodoItemOperation(state, action) {
// TODO: Implement "deleteTodoItemOperation" reducer
throw new Error('Reducer "deleteTodoItemOperation" not yet implemented');
},
};
Implement the operation reducers​
Let's implement each reducer one by one.
Step 1: Add the import​
First, add the generateId import at the top of the file:
import { generateId } from "document-model/core";
import type { TodoListTodosOperations } from "todo-tutorial/document-models/todo-list";
Step 2: Implement addTodoItemOperation​
Replace the boilerplate addTodoItemOperation with the actual implementation:
export const todoListTodosOperations: TodoListTodosOperations = {
addTodoItemOperation(state, action) {
// TODO: Implement "addTodoItemOperation" reducer
throw new Error('Reducer "addTodoItemOperation" not yet implemented');
},
addTodoItemOperation(state, action) {
const id = generateId();
state.items.push({ ...action.input, id, checked: false });
},
updateTodoItemOperation(state, action) {
// ...
},
deleteTodoItemOperation(state, action) {
// ...
},
};
What's happening here:
- We generate a unique ID using
generateId()fromdocument-model/core - We push a new item to the
itemsarray with the input text, new ID, andchecked: false - Under the hood, Powerhouse uses Immer.js, so this "mutation" is actually immutable
Step 3: Implement updateTodoItemOperation​
Replace the boilerplate updateTodoItemOperation:
updateTodoItemOperation(state, action) {
// TODO: Implement "updateTodoItemOperation" reducer
throw new Error('Reducer "updateTodoItemOperation" not yet implemented');
},
updateTodoItemOperation(state, action) {
const item = state.items.find((item) => item.id === action.input.id);
if (!item) return;
item.text = action.input.text ?? item.text;
item.checked = action.input.checked ?? item.checked;
},
What's happening here:
- We find the item by its ID
- We return early if the item is not found
- We use nullish coalescing (
??) to only update fields that are provided
Step 4: Implement deleteTodoItemOperation​
Replace the boilerplate deleteTodoItemOperation:
deleteTodoItemOperation(state, action) {
// TODO: Implement "deleteTodoItemOperation" reducer
throw new Error('Reducer "deleteTodoItemOperation" not yet implemented');
},
deleteTodoItemOperation(state, action) {
state.items = state.items.filter((item) => item.id !== action.input.id);
},
What's happening here:
- We filter out the item with the matching ID
- This creates a new array without the deleted item
Complete reducer file​
Here's the complete implementation:
Complete todos.ts
import { generateId } from "document-model/core";
import type { TodoListTodosOperations } from "todo-tutorial/document-models/todo-list";
export const todoListTodosOperations: TodoListTodosOperations = {
addTodoItemOperation(state, action) {
const id = generateId();
state.items.push({ ...action.input, id, checked: false });
},
updateTodoItemOperation(state, action) {
const item = state.items.find((item) => item.id === action.input.id);
if (!item) return;
item.text = action.input.text ?? item.text;
item.checked = action.input.checked ?? item.checked;
},
deleteTodoItemOperation(state, action) {
state.items = state.items.filter((item) => item.id !== action.input.id);
},
};
To make sure everything works as expected:
# Check types compile correctly
pnpm tsc
# Check linting passes
pnpm lint
# Compare with reference implementation
git diff tutorial/step-3-implement-reducer-operation-handlers -- document-models/todo-list/src/reducers/
Up next: Writing tests​
In the next chapter, you'll write comprehensive tests to verify your reducer implementations work correctly.