Skip to content

fix: React custom blocks cause infinite rerender on mobile (BLO-1212)#2830

Open
matthewlipski wants to merge 1 commit into
mainfrom
mobile-react-infinite-rerender
Open

fix: React custom blocks cause infinite rerender on mobile (BLO-1212)#2830
matthewlipski wants to merge 1 commit into
mainfrom
mobile-react-infinite-rerender

Conversation

@matthewlipski
Copy link
Copy Markdown
Collaborator

@matthewlipski matthewlipski commented Jun 4, 2026

Summary

This PR fixes an issue with React custom blocks sometimes causing infinite re-render loops on mobile. This can be seen most easily with the Alert Block with Full UX example, where attempting to insert an alert block via the slash menu will result in an infinite re-render loop and crash. This can be replicated via the mobile emulator in Chrome dev tools.

The reason this happens is that the way ProseMirror detects when a block's editable content has changed is different on desktop vs mobile. On desktop, it's detected using key events within the editable content wrapper element. On mobile though, key inputs don't use actually use typical key events, but insert characters using IME composition (see here and here). Therefore, ProseMirror instead detects changes in the editable content based on DOM mutations. Yet for some reason, re-renders are triggered by DOM mutations anywhere in the block, not just within its editable content.

And so the problem is that React's own rendering also causes DOM mutations, leading to ProseMirror triggering a re-render, causing React to render the block again, and starting an infinite loop. Therefore, we need to explicitly tell ProseMirror to ignore mutations outside the editable content to avoid this from happening.

Closes #2804

Rationale

This is a severe issue on mobile.

Changes

  • Added ignoreMutation to createReactBlockSpec to ignore DOM mutations outside of block's editable content.

Impact

N/A

Testing

N/A (we don't have testing infrastructure on mobile)

Screenshots/Video

N/A

Checklist

  • Code follows the project's coding standards.
  • Unit tests covering the new feature have been added.
  • All existing tests pass.
  • The documentation has been updated to reflect the new feature

Additional Notes

N/A

Summary by CodeRabbit

  • Performance
    • Enhanced block component rendering performance by reducing unnecessary updates, resulting in a smoother and more responsive editing experience.

@vercel
Copy link
Copy Markdown

vercel Bot commented Jun 4, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
blocknote Error Error Jun 4, 2026 9:11am
blocknote-website Error Error Jun 4, 2026 9:11am

Request Review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 4, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds an ignoreMutation handler to the React node-view rendering options in ReactBlockSpec.tsx. The handler filters out selection mutations and mutations targeting the block's [data-node-view-content] wrapper element, allowing only external mutations to trigger ProseMirror updates.

Changes

React node-view mutation filtering

Layer / File(s) Summary
ignoreMutation handler in ReactNodeViewRenderer
packages/react/src/schema/ReactBlockSpec.tsx
Adds mutation filtering logic that early-returns for selection mutations, locates the [data-node-view-content] wrapper, and ignores mutations targeting the wrapper or outside it; other mutations proceed to trigger default updates.

Estimated Code Review Effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Suggested Reviewers

  • YousefED
  • nperez0111

Poem

A rabbit hops through mutations with care,
Filtering noise from the update air,
React's gentle changes now flow just right,
No more freezes on mobile's bright light! 🐰✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed Title clearly and specifically describes the main change: fixing an infinite rerender issue in React custom blocks on mobile, with issue reference.
Description check ✅ Passed Description covers key sections (Summary, Rationale, Changes, Impact, Testing, Checklist) with detailed technical explanation of the problem and solution.
Linked Issues check ✅ Passed The PR addresses the linked issue #2804 by implementing the ignoreMutation handler to prevent infinite rerenders caused by React DOM mutations outside the editable content wrapper.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing the mobile infinite rerender issue in React custom blocks, matching the linked issue requirements.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch mobile-react-infinite-rerender

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Jun 4, 2026

Open in StackBlitz

@blocknote/ariakit

npm i https://pkg.pr.new/@blocknote/ariakit@2830

@blocknote/code-block

npm i https://pkg.pr.new/@blocknote/code-block@2830

@blocknote/core

npm i https://pkg.pr.new/@blocknote/core@2830

@blocknote/mantine

npm i https://pkg.pr.new/@blocknote/mantine@2830

@blocknote/react

npm i https://pkg.pr.new/@blocknote/react@2830

@blocknote/server-util

npm i https://pkg.pr.new/@blocknote/server-util@2830

@blocknote/shadcn

npm i https://pkg.pr.new/@blocknote/shadcn@2830

@blocknote/xl-ai

npm i https://pkg.pr.new/@blocknote/xl-ai@2830

@blocknote/xl-docx-exporter

npm i https://pkg.pr.new/@blocknote/xl-docx-exporter@2830

@blocknote/xl-email-exporter

npm i https://pkg.pr.new/@blocknote/xl-email-exporter@2830

@blocknote/xl-multi-column

npm i https://pkg.pr.new/@blocknote/xl-multi-column@2830

@blocknote/xl-odt-exporter

npm i https://pkg.pr.new/@blocknote/xl-odt-exporter@2830

@blocknote/xl-pdf-exporter

npm i https://pkg.pr.new/@blocknote/xl-pdf-exporter@2830

commit: 57951dc

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 4, 2026

PR Preview Action v1.8.1

QR code for preview link

🚀 View preview at
https://TypeCellOS.github.io/BlockNote/pr-preview/pr-2830/

Built to branch gh-pages at 2026-06-04 08:49 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/react/src/schema/ReactBlockSpec.tsx (1)

318-350: 🏗️ Heavy lift

Add a mobile regression test for mutation-type filtering.

Lines [318-350] implement behavior that is sensitive to mutation type and target; without regression coverage, this is easy to break. Add a test that asserts wrapper attributes mutations are ignored, while content childList/characterData mutations are not ignored.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/react/src/schema/ReactBlockSpec.tsx` around lines 318 - 350, Add a
regression test for the ReactBlockSpec.ignoreMutation behavior: create a test
that constructs a node-view using the ReactBlockSpec (or the same block spec
used in tests) and simulates mutations against the wrapper element and the inner
editable content; assert that an attributes mutation on the editable wrapper
returns true (ignored), while childList and characterData mutations on the
content return false (not ignored). Target the ignoreMutation function in
ReactBlockSpec.tsx and exercise cases for mutation.type === "selection" (should
return false), an attributes mutation with mutation.target === content wrapper
(true), and content childList/characterData mutations where mutation.target is
inside [data-node-view-content] (false).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/react/src/schema/ReactBlockSpec.tsx`:
- Around line 343-347: In ReactBlockSpec.tsx inside the ignoreMutation
implementation (the ignoreMutation function used by the ReactNodeView), stop
returning true for all mutation types when mutation.target === content; instead
only ignore wrapper attribute changes by checking that mutation.type ===
"attributes" && mutation.target === content before returning true so
childList/textInput mutations on the [data-node-view-content] element are not
suppressed.

---

Nitpick comments:
In `@packages/react/src/schema/ReactBlockSpec.tsx`:
- Around line 318-350: Add a regression test for the
ReactBlockSpec.ignoreMutation behavior: create a test that constructs a
node-view using the ReactBlockSpec (or the same block spec used in tests) and
simulates mutations against the wrapper element and the inner editable content;
assert that an attributes mutation on the editable wrapper returns true
(ignored), while childList and characterData mutations on the content return
false (not ignored). Target the ignoreMutation function in ReactBlockSpec.tsx
and exercise cases for mutation.type === "selection" (should return false), an
attributes mutation with mutation.target === content wrapper (true), and content
childList/characterData mutations where mutation.target is inside
[data-node-view-content] (false).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 715e9430-fa98-4470-b143-703543153e9f

📥 Commits

Reviewing files that changed from the base of the PR and between ec9c151 and 57951dc.

📒 Files selected for processing (1)
  • packages/react/src/schema/ReactBlockSpec.tsx

Comment on lines +343 to +347
// Also ignore mutations for the editable content wrapper
// element.
if (mutation.target === content) {
return true;
}
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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

In ProseMirror NodeView ignoreMutation, should childListmutations targetingcontentDOM be ignored, or treated as content changes that must be handled?

💡 Result:

In ProseMirror NodeViews, childList mutations targeting contentDOM should generally not be ignored [1]. If you ignore mutations within contentDOM, you prevent ProseMirror from detecting changes to the document content, which effectively breaks the editor's ability to sync the DOM with the document state [1][2][3]. The ignoreMutation method is intended to give you control over which DOM changes ProseMirror should observe [2][3]. By returning false, you instruct ProseMirror to re-read the selection or re-parse the range around the mutation [2][3]. Since contentDOM is the container ProseMirror manages to hold the node's children, it is crucial that ProseMirror remains aware of modifications made to it so it can reconcile them with the underlying document model [4][1]. Common practices for ignoreMutation include: 1. Always returning false for mutations that occur within contentDOM, as these are legitimate document content changes [1]. 2. Returning true for mutations that occur in parts of the NodeView's DOM that ProseMirror does not manage (such as custom UI wrappers or non-editable nodes) [1]. 3. Being careful with mutations that occur on the container itself; in some cases, it is necessary to allow ProseMirror to handle these to prevent state desynchronization or issues with cursor positioning [1]. If you find that your NodeView is causing performance issues or unwanted re-renders, it is usually better to ensure that your update method is correctly implemented or that your contentDOM is properly configured, rather than broadly ignoring mutations within it [5][6]. Ignoring mutations within contentDOM is generally counterproductive to the editor's core functionality [1][2].

Citations:


Don’t ignore all wrapper-targeted mutations.
In packages/react/src/schema/ReactBlockSpec.tsx lines ~343-347, ignoreMutation returns true for all mutation types whenever mutation.target === content (the closest [data-node-view-content] wrapper), which also suppresses childList mutations that can represent real editable-content updates (e.g., mobile/IME). Narrow the ignore to wrapper attributes mutations only.

Proposed fix
-                  if (mutation.target === content) {
+                  if (
+                    mutation.target === content &&
+                    mutation.type === "attributes"
+                  ) {
                     return true;
                   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Also ignore mutations for the editable content wrapper
// element.
if (mutation.target === content) {
return true;
}
// Also ignore mutations for the editable content wrapper
// element.
if (
mutation.target === content &&
mutation.type === "attributes"
) {
return true;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/react/src/schema/ReactBlockSpec.tsx` around lines 343 - 347, In
ReactBlockSpec.tsx inside the ignoreMutation implementation (the ignoreMutation
function used by the ReactNodeView), stop returning true for all mutation types
when mutation.target === content; instead only ignore wrapper attribute changes
by checking that mutation.type === "attributes" && mutation.target === content
before returning true so childList/textInput mutations on the
[data-node-view-content] element are not suppressed.

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.

Alert Block freezes editor when selected from Slash Menu on mobile

1 participant