Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions packages/lexical/src/LexicalUpdates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -544,7 +544,7 @@ export function $commitPendingUpdates(
// may have been added by a prior update (e.g. via $onUpdate inside
// editor.focus()). This can happen when another commit consumed
// the pending editor state before this scheduled commit ran.
if (editor._deferred.length > 0) {
if (!editor._updating && editor._deferred.length > 0) {
triggerDeferredUpdateCallbacks(editor, editor._deferred);
}
return;
Expand Down Expand Up @@ -632,7 +632,6 @@ export function $commitPendingUpdates(
const dirtyElements = editor._dirtyElements;
const normalizedNodes = editor._normalizedNodes;
const tags = editor._updateTags;
const deferred = editor._deferred;

if (needsUpdate) {
editor._dirtyType = NO_DIRTY_NODES;
Expand Down Expand Up @@ -750,7 +749,13 @@ export function $commitPendingUpdates(
prevEditorState: recoveryEditorState || currentEditorState,
tags,
});
triggerDeferredUpdateCallbacks(editor, deferred);
// A commit can be forced while an outer update is still running (for
// example, setEditorState() inside editor.update()). Keep $onUpdate
// callbacks queued so the outer update drains them after updateFn returns.
if (!previouslyUpdating) {
const deferred = editor._deferred;
triggerDeferredUpdateCallbacks(editor, deferred);
}
$triggerEnqueuedUpdates(editor);
}

Expand Down
48 changes: 48 additions & 0 deletions packages/lexical/src/__tests__/unit/LexicalEditor.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2674,6 +2674,54 @@ describe('LexicalEditor tests', () => {
expect(historicCommit!.tags).toContain('historic-tag');
});

it('defers onUpdate callbacks when setEditorState commits inside an update', async () => {
init();
editor.update(
() => {
const paragraph = $createParagraphNode();
paragraph.append($createTextNode('historic'));
$getRoot().append(paragraph);
},
{discrete: true},
);
const historicState = editor.getEditorState();
editor.update(
() => {
const root = $getRoot();
root.clear();
const paragraph = $createParagraphNode();
paragraph.append($createTextNode('current'));
root.append(paragraph);
},
{discrete: true},
);

const events: Array<string> = [];
editor.update(
() => {
events.push('update start');
editor.setEditorState(historicState);
events.push('update end');
},
{
onUpdate: () => {
events.push('onUpdate');
},
},
);
events.push('after update');

expect(events).toEqual(['update start', 'update end', 'after update']);

await Promise.resolve();
expect(events).toEqual([
'update start',
'update end',
'after update',
'onUpdate',
]);
});

it('mutation listeners does not trigger when other node types are mutated', async () => {
init();

Expand Down
Loading