Programmatic changes in tlDraw without affecting undo/redo stack

While working with the tlDraw framework for infinite canvases, you may want to make programmatic changes to the content in the editor without the user being aware of them. That means that some of your modifications shouldn’t appear in the undo stack.

If you let your programmatic changes register on the undo stack, often, each user initiated action could result in multiple programmatic changes behind the scenes. Meaning if the user tries to undo their action, they have to undo several other changes as well. Changes that they didn’t event realise occurred.

This article looks at handling that.

An example problem

Imagine you create a flowchart app that allows the user to drag flowchart bubbles around. When they drag a bubble close to another bubble, the app automatically connects them with a line.

The problem

In this example, the user might drag a bubble (One user action), but upon releasing their mouse, the app automatically draws a line between the bubbles, adds a label to the line, and changes the colour of the bubbles (Three programmatic changes).

By default, this creates four undo entries. one for the bubble colour changes, one for the label, one for the line, and one for the bubble drag. But as far as the user is aware, they only did one action, so if they choose to undo, they expect to only have to undo once.

Problem code

If you do the below, it will add an entry onto the undo stack for the colour changes.

editor.updateShapes([
    {
        id: shapeId1,
        type: 'bubble',
        props: {
            color: 'green'
        }
    },
    {
        id: shapeId2,
        type: 'bubble',
        props: {
            color: 'green'
        }
    }
]);

The solution

There are a couple of ways to handle this and your approach should change depending on how your program works.

Squash or discard

One way to handle this would be to squash all of the changes into one undo entry. This means that when the user selects undo once, tlDraw will undo the colour change, undo the label, undo the line, and undo the bubble drag as if they were all one action. This may be the option you want, but not necessarily in this case.

In our instance, we have likely programmed the app to programmatically reassess lines, labels, and colours whenever anything changes, so we don’t actually need to undo those. Instead, we undo the user’s bubble drag, and everything else will recalculate automatically – But bare in mind, your app needs to be built to work like this (ie. after every tldraw change, it recalculates and adds/removes what’s needed).

Essentially, in this example, we can tell tlDraw’s undo stack to ignore any of the changes we did programmatically. To do this, when we make the changes, we pass a flag to the method to indicate it is ephemeral.

Solution code

editor.updateShapes([
    {
        id: shapeId1,
        type: 'bubble',
        props: {
            color: 'green'
        }
    },
    {
        id: shapeId2,
        type: 'bubble',
        props: {
            color: 'green'
        }
    }
], {
    ephemeral: true,
});

That’s it!

For the official documentation on this, refer to the tlDraw documentation for TLCommandHistoryOptions (Which is the object we’ve passed in above), and the editor.updateShapes method it’s passed into.

There are also two other history options we haven’t directly address here (squashing, and preservesRedoStack). But let’s leave them for another article.

I’d love to know if any of these articles helped you.
Share what you’re building or ask me a question on Threads or somewhere else.

Instagram is a great place to see my final creative coding experiments, while the others are are great place to connect or see progress updates.

If my content has helped you, I’d never say no to donations to help me keep writing.

Here are some other things you might like


Author:

Date:

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.