cms-admin: Fix block editor keeping the previously selected block's content - #6130
cms-admin: Fix block editor keeping the previously selected block's content#6130SebiVPS wants to merge 3 commits into
Conversation
…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>
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 😁 |
Wouldn't the more appropriate fix be to provide |
VPS-Obi
left a comment
There was a problem hiding this comment.
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.
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.tsx—StackPage name="blocks"createListBlock.tsx—StackPage name="edit"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
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.