Skip to content

cms-admin: Fix block editor keeping the previously selected block's content - #6130

Open
SebiVPS wants to merge 3 commits into
mainfrom
fix/block-editor-remount-on-selection-change
Open

cms-admin: Fix block editor keeping the previously selected block's content#6130
SebiVPS wants to merge 3 commits into
mainfrom
fix/block-editor-remount-on-selection-change

Conversation

@SebiVPS

@SebiVPS SebiVPS commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Summary

Clicking a block in the block preview opens that block's editor in the block list. If the newly selected block had the same type as the previously selected one, the editor kept showing the previous block's content — so after the first click, the panel appeared unresponsive.

Most visible with the TipTap rich text block: select one rich text block in the preview, then click a different one, and the editor still holds the first block's text.

Root cause

Two block factories render the active block's admin component without a key:

  • createBlocksBlock.tsxStackPage name="blocks"
  • createListBlock.tsxStackPage name="edit"
<block.AdminComponent state={match.props} updateState={createUpdateSubBlocksFn(id)} />

The routing layer is correct: the SelectComponent message arrives, history.push navigates, and state.blocks.find((c) => c.key === id) resolves the right block, passing the new state down. The problem is purely React reconciliation — the element sits at the same tree position with the same component type and no key, so React re-renders the mounted component rather than remounting it.

That is fine for editors that render straight from props, but not for editors that build internal state on mount. createTipTapRichTextBlock calls useEditor({ ..., content: state.tipTapContent }) without a deps argument, so @tiptap/react defaults to deps = []. With an empty deps, the hook never takes the refreshEditorInstance path and instead only calls editor.setOptions(). setOptions merges the new options but never rebuilds the document — options.content is consumed solely by createDoc() at construction time. The ProseMirror document therefore keeps whatever content it was created with.

This is also why the bug is type-specific: switching to a block of a different type swaps in a different component function, which forces a genuine unmount/mount and reloads the content correctly.

Changes

Key the admin component by the selected block's key in both factories, so React remounts it whenever the selection changes.

-<block.AdminComponent state={match.props} updateState={createUpdateSubBlocksFn(id)} />
+<block.AdminComponent key={id} state={match.props} updateState={createUpdateSubBlocksFn(id)} />

Keying at this level was chosen over passing deps to useEditor in createTipTapRichTextBlock, because it fixes the class of problem — any block editor holding mount-time internal state — rather than only the TipTap case.

Reproduction

  1. Open a page or article with two or more rich text blocks in the admin.
  2. Go to the Content tab.
  3. Click one rich text block in the preview — its editor opens correctly.
  4. Click a different rich text block in the preview.
  5. Before: the editor still shows the block from step 3. After: it shows the newly selected block.

Verification

Verified manually in a consuming project by applying the equivalent change to the compiled @comet/cms-admin in node_modules: after the change, selecting successive rich text blocks in the preview updates the editor each time. Selecting blocks of differing types continued to work as before.
Verified in demo before and after the change.

…content

Clicking a block in the block preview opens that block's editor in the block
list. When the newly selected block had the same type as the previous one,
React reused the already mounted admin component instead of remounting it,
since it sits at the same position in the tree with the same component type
and no key. Editors that build internal state on mount — most visibly the
TipTap rich text block, whose document is created once from the initial
content — kept rendering the previously selected block's content, so the panel
looked unresponsive to every click after the first.

Key the admin component by the selected block so React remounts it whenever
the selection changes. This affects both createBlocksBlock and createListBlock,
which shared the same omission.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@SebiVPS SebiVPS self-assigned this Aug 6, 2026
@SebiVPS SebiVPS changed the title cms-admin: Fix block editor keeping the previously selected block cms-admin: Fix block editor keeping the previously selected block's content Aug 6, 2026
@SebiVPS
SebiVPS marked this pull request as ready for review August 6, 2026 08:41
@SebiVPS
SebiVPS requested a review from VPS-Obi August 6, 2026 08:41
@VPS-Obi

VPS-Obi commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Two things worth deciding before you open it:

Verification is single-path. I confirmed the fix against the TipTap rich text case only, via the patched compiled package. Adding a key changes remount behaviour for every block admin component, so it's worth a quick pass over other stateful editors (the list block's edit page especially) to be sure nothing relied on the old re-render-in-place behaviour — e.g. anything holding scroll position or uncommitted local edits across a selection change.

Comet is on GitHub, so this will be a pull request rather than a merge request — the description works either way, but gh pr create is the tool, not the GitLab MCP.

Please update the PR description by fixing the formatting and removing chat parts (the PR description will be the squashed commit's description). Or tell Claude to update it 😁

@VPS-Obi
VPS-Obi requested a review from nsams August 6, 2026 12:15
@VPS-Obi

VPS-Obi commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

That is fine for editors that render straight from props, but not for editors that build internal state on mount. createTipTapRichTextBlock calls useEditor({ ..., content: state.tipTapContent }) without a deps argument, so @tiptap/react defaults to deps = []. With an empty deps, the hook never takes the refreshEditorInstance path and instead only calls editor.setOptions(). setOptions merges the new options but never rebuilds the document — options.content is consumed solely by createDoc() at construction time. The ProseMirror document therefore keeps whatever content it was created with.

Wouldn't the more appropriate fix be to provide state.tipTapContent as dependency for useEditor()? @nsams what do you think? Or would this result in an infinite update loop?

@VPS-Obi VPS-Obi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding a key to the admin component is probably a good idea (I wonder why this never made problems up until now). Still, we should fix the root cause as well, which is the missing dependency array for useEditor IMO.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants