In this guide, we’ll be learning how to use Liveblocks multiplayer undo/redowith Zustand using the APIs from the [@liveblocks/zustand
][] package.
This guide assumes you already have Liveblocks set up into your Zustand store.If you don’t make sure to followthese quick steps to get started first.
Implementing undo/redo when multiple users can edit the app state simultaneouslyis quite complex!
When only one user can edit the app state, undo/redo acts like a "timemachine"; undo/redo replaces the current app state with an app state that wasalready be seen by the user.
When multiple users are involved, undo or redo can lead to an app state that noone has seen before. For example, let's imagine a design tool with two usersediting the same circle.
{ radius: "10px", color: "yellow" }
color
toblue
=>{ radius: "10px", color: "blue" }
radius
to20px
=>{ radius: "20px", color: "blue" }
{ radius: "20px", color: "yellow" }
A yellow circle with a radius of 20px in a completely new state.Undo/redo ina multiplayer app does not act like a "time machine"; it only undoes localoperation.
The good news is thatroom.history.undo
androom.history.redo
takesthat complexity out of your hands so you can focus on the core features of yourapp.
Access these two functions from your store like below so you can easily bindthem to keyboard events (⌘+Z/⌘+⇧+Z on Mac and Ctrl+Z/Ctrl+Y on Windows) or undoand redo buttons in your application..
importuseStorefrom"../store";
functionYourComponent(){const undo=useStore((state)=> state.liveblocks.room?.history.undo);const redo=useStore((state)=> state.liveblocks.room?.history.redo);
return(<><buttononClick={undo}>Undo</button><buttononClick={redo}>Redo</button></>);}
Some applications require skipping intermediate states from the undo/redohistory. Let's consider a design tool; when a user drags a rectangle, theintermediate rectangle positions should not be part of the undo/redo history.But they should be shared with the rest of the room to create a greatexperience.
room.history.pause
androom.history.resume
lets you skip theseintermediate states. To go back to our design tool example, the sequence ofcalls would look like that:
room.history.pause
to skip future operations from the historyroom.history.resume
At this point, if the user callsroom.history.undo
, the rectangle will go backto its initial position.
importuseStorefrom"../store";
const pause=useStore((state)=> state.liveblocks.room?.history.pause);const resume=useStore((state)=> state.liveblocks.room?.history.resume);
We use cookies to collect data to improve your experience on our site. Read ourPrivacy Policy to learn more.