When You Want to Serialize User Actions - Unexpected Benefits of Command Pattern
While implementing "save immediately to server upon field edit" in a task management system, I hit a wall - "how to handle edits during offline". The Command pattern not only solved this but brought unexpected benefits.
While implementing “save immediately to server upon field edit” in a task management system, I hit a wall. The wall was: how to handle the situation when “user edits a field during offline”.
Simply waiting for network recovery would confuse users about what was saved and what wasn’t.
Why Command Pattern?
The requirement was: during offline, store user operations and execute them in order once network recovers.
// User operation: Change task titleconst command = { type: 'UPDATE_TASK_TITLE', taskId: '123', newTitle: 'Updated title', timestamp: Date.now()};
// Store in queue during offlineofflineQueue.push(command);
// Execute in order when network recoversawait executeCommands(offlineQueue);Structuring operations this way made it possible to store and replay them. This is exactly the Command pattern (encapsulating operations as objects).
Unexpected Benefits
Initially I only wanted offline support, but I gained additional benefits:
1. Undo/Redo Became Trivial
// Undo historyconst history = [cmd1, cmd2, cmd3];
// Undoconst lastCommand = history.pop();await lastCommand.undo();
// Redohistory.push(lastCommand);await lastCommand.execute();Since operations were objects, implementing undo/redo was just a matter of managing a stack.
2. Testing Became Easier
// Test: Does title update work correctly?const command = new UpdateTaskTitleCommand('123', 'New title');await command.execute();
expect(getTask('123').title).toBe('New title');Since each operation was an independent object, I could test them in isolation.
3. Could Implement Optimistic UI
// Immediately reflect in UI (optimistic update)updateUIOptimistically(command);
// Execute in backgroundcommand.execute() .catch(() => { // Rollback if failed revertUI(command); });Separating “execute” from “display” made implementing optimistic UI natural.
Summary
While implementing “save immediately upon field edit” in a task management system, I hit a wall with “how to handle edits during offline”. Using Command pattern not only solved this but brought unexpected benefits like easy undo/redo implementation and simplified testing.
Particularly, structuring operations as objects made many frontend patterns (offline queue, optimistic UI, undo/redo) work naturally together.