Undo
Because of Replicache's conflict resolution model and undo manager, adding undo to your Replicache app is easy. All of the complexities of multi player undo are handled by Replicache. This HOWTO will walk through the process of adding undo to our sample todo app.
Add undo library
npm install @rocicorp/undo
Instantiate UndoManager
const undoManager = new UndoManager();
Use the undo manager to associate executed mutations with undo mutations
It is important to keep track of all the mutations you want to undo. For every mutation that you want to perform, you will need an inverse mutation for undo.
Instead of calling Replicache mutations directly you will want to wrap the calls using undoManager.add
.
// new item with undo
const handleNewItem = (text: string) => {
const id = nanoid();
undoManager.add({
execute: () => {
rep.mutate.putTodo({
id,
text: text,
sort: todos.length > 0 ? todos[todos.length - 1].sort + 1 : 0,
completed: false,
});
};,
undo: () => rep.mutate.deleteTodos([id]),
});
};
Calling undo
and redo
functions
It is as easy as calling undoManager.undo
and undoManager.redo
to undo or redo your mutations.
Add keyboard bindings for undo
/ redo
(optional)
Usually you want to bind keyboard events to undo / redo. (ctrl+z, cmd+z, command+shift+z, ctrl+shift+z). You can capture keyboard events or use a library like react-hotkeys
.
const handlers = {
undo: () => undoManager.undo(),
redo: () => undoManager.redo(),
};
const keyMap = {
undo: ["ctrl+z", "command+z"],
redo: ["ctrl+y", "command+shift+z", "ctrl+shift+z"],
};
return (
<Hotkeys
{...{
keyMap,
handlers,
}}
>
...
</HotKeys>
Basic Example
import { Replicache } from "replicache";
import { useSubscribe } from "replicache-react";
...
import { UndoManager } from "@rocicorp/undo";
import { HotKeys } from "react-hotkeys";
// Replicache and UndoManager are initialized outside of the initial component render.
// undoManager = new UndoManager()
const App = ({ rep }: { rep: Replicache<M>; undoManager: UndoManager }) => {
const todos = useSubscribe(rep, listTodos, [], [rep]);
// new item with undo
const handleNewItem = (text: string) => {
const id = nanoid();
undoManager.add({
execute: () => {
rep.mutate.putTodo({
id,
text: text,
sort: todos.length > 0 ? todos[todos.length - 1].sort + 1 : 0,
completed: false,
});
};,
undo: () => rep.mutate.deleteTodos([id]),
});
};
};
const handlers = {
undo: () => undoManager.undo(),
redo: () => undoManager.redo(),
};
const keyMap = {
undo: ["ctrl+z", "command+z"],
redo: ["ctrl+y", "command+shift+z", "ctrl+shift+z"],
};
return (
<Hotkeys
{...{
keyMap,
handlers,
}}
>
<Header onNewItem={handleNewItem} />
<MainSection todos={todos} />
</Hotkeys>
);
};
Undo
library can handle more complex features like grouping
and onChange
events. You can look at full integrations of undo with the Replidraw and Repliear projects. Please reference the Undo library for more information.