Building It Step By Step
Undo and Redo System
Learn how to add undo and redo capabilities to navigate between previous and future states.
Getting Started
Navigate to the corresponding folder:
cd step-6-undo-and-redo
Install the dependencies:
npm install
Then run the project:
npm run dev
Undo and Redo
The undo and redo functionality is implemented in the useWorkflow hook.
// src/shared/kits/workflow/hooks/use-workflow.ts
// ...
function reducer(state: State, action: Action): State {
// ...
}
function init(workflow: Workflow): State {
return {
workflows: [workflow],
pointer: 0,
current: workflow,
};
}
export function useWorkflow(schema: WorkflowSchema): [State, Dispatch<Action>] {
return useReducer(reducer, schema, init);
}
This hook returns an object with the following properties:
workflows: The workflow states used for undo/redo, including past and future states.pointer: The index inworkflowsrepresenting the current position in the history.current: The currently active workflow state.
The reducer handles both undo/redo logic and the application of workflow changes.
When applying a change, applyWorkflowChange returns a memory property that defines whether that change has to be saved in the history, as shown here.
// src/shared/kits/workflow/hooks/use-workflow.ts
// ...
function reducer(state: State, action: Action): State {
switch (action.type) {
/**
* Applies all incoming workflow changes. Each change may request the resulting
* workflow to be saved in the history (`memory`), in which case a new entry
* is added and any forward history (redo states) is discarded.
*/
case "applyWorkflowChanges": {
const workflows: Workflow[] = [...state.workflows];
let current: Workflow = state.current;
let pointer: number = state.pointer;
for (const change of action.changes) {
const output = applyWorkflowChange(change, current);
current = output.workflow;
if (output.memory) {
pointer++;
workflows.splice(pointer, workflows.length - pointer, {
...output.workflow,
widget: null,
active: null,
});
}
}
return { workflows: workflows, pointer, current };
}
// ...
}
}
// ...
This is because not all changes, such as selecting or dragging nodes, need to be saved.