Skip to content

fix(core): guard getBlock() calls to prevent TypeError on stale blocks - #2941

Merged
nperez0111 merged 2 commits into
mainfrom
issue-2907
Aug 4, 2026
Merged

fix(core): guard getBlock() calls to prevent TypeError on stale blocks#2941
nperez0111 merged 2 commits into
mainfrom
issue-2907

Conversation

@nperez0111

@nperez0111 nperez0111 commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Summary

Guard all unguarded editor.getBlock()! non-null assertions across the codebase to prevent uncaught TypeErrors when blocks are removed from the document while closures still reference them.

Rationale

When a block is removed (via undo, collaborative editing, replaceBlocks, or menu actions that recreate the block), closed-over block references become stale and getBlock() returns undefined. The non-null assertion (!) lets undefined pass through, causing uncaught TypeError: Cannot read properties of undefined (reading 'id') in DOM event handlers and React callbacks. These errors escape React error boundaries and land in window.onerror, making them indistinguishable from real crashes in error monitoring. This is the same class of bug previously fixed in TableHandles (#2821/#2847).

Changes

  • createToggleWrapper.ts: Guard 4 getBlock(block)! calls — early-return in click handler, conditional guard in onChange callback.
  • ToggleWrapper.tsx (React): Guard 3 getBlock(block)! calls — early-return in handleToggle, return 0 in useEditorState selector, pass block directly to click handler.
  • SideMenu.ts: Extract getBlock() into a variable and guard before state assignment.
  • TableHandles.ts: Remove misleading ! assertion (line already has a null check on the next line).
  • handleFileInsertion.ts: Guard getBlock(id)! and downstream insertedBlockId usage.
  • rebaseTool.ts (markdown): Add guard matching the HTML counterpart's existing pattern.
  • EmbedTab.tsx / UploadTab.tsx: Remove !, add null guard after hooks (respecting React rules of hooks).

Impact

No functional changes for the happy path. Stale block references now silently bail out instead of throwing uncaught errors. This matches the existing guarded patterns already used elsewhere in the codebase (optional chaining on the same getBlock() calls, the #2821 TableHandles fix).

Testing

  • vp run lint passes with 0 errors.
  • vp run build succeeds across all packages.
  • vp run test passes all 695+ unit tests across all packages.

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

No unit tests added — the bug requires a stale block reference from a removed DOM node, which is difficult to reproduce deterministically (the issue reporter also could not find reliable manual repro steps). The fix is a defensive guard pattern consistent with existing code.

Fixes #2907

Summary by CodeRabbit

  • Bug Fixes
    • Improved stability when blocks are deleted or unavailable during file uploads, embeds, toggles, table interactions, and side-menu actions.
    • Prevented invalid updates and UI errors when referenced blocks no longer exist.
    • Added clearer handling for unavailable blocks during Markdown-related operations.
    • Upload and embed panels now safely stop rendering when their associated block cannot be found.

When a block is removed from the document (via undo, collaboration, or
replaceBlocks), closed-over references become stale and getBlock()
returns undefined. The non-null assertion (!) let undefined through,
causing uncaught TypeErrors in DOM event handlers and React callbacks.

Replace all getBlock()! assertions with proper undefined guards across
toggle wrappers, side menu, table handles, file panel tabs, file
insertion handler, and AI rebase tool.

Fixes #2907
@vercel

vercel Bot commented Aug 4, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
blocknote Ready Ready Preview Aug 4, 2026 8:18am
blocknote-website Ready Ready Preview Aug 4, 2026 8:18am

Request Review

@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 5c89325b-8229-4b15-8000-a882b82c63b3

📥 Commits

Reviewing files that changed from the base of the PR and between 9dd7fd9 and 12e11aa.

📒 Files selected for processing (4)
  • packages/core/src/extensions/SideMenu/SideMenu.ts
  • packages/core/src/extensions/TableHandles/TableHandles.ts
  • packages/react/src/components/FilePanel/DefaultTabs/EmbedTab.tsx
  • packages/react/src/components/FilePanel/DefaultTabs/UploadTab.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/core/src/extensions/TableHandles/TableHandles.ts
  • packages/react/src/components/FilePanel/DefaultTabs/EmbedTab.tsx
  • packages/core/src/extensions/SideMenu/SideMenu.ts

📝 Walkthrough

Walkthrough

The changes add missing-block guards across clipboard insertion, toggle interactions, side-menu and table-handle updates, React block components, file panels, and Markdown rebasing. Non-null assertions are replaced with early returns, nullable handling, or explicit errors.

Changes

Stale block guards

Layer / File(s) Summary
Clipboard file insertion guards
packages/core/src/api/clipboard/fromClipboard/handleFileInsertion.ts
Drop insertion stops when the target block is absent. File upload and block updates stop when no block ID is produced.
Core editor interaction guards
packages/core/src/blocks/ToggleWrapper/createToggleWrapper.ts, packages/core/src/extensions/SideMenu/SideMenu.ts, packages/core/src/extensions/TableHandles/TableHandles.ts
Toggle, side-menu, and table-handle updates validate block lookups before using block state.
React block UI guards
packages/react/src/blocks/ToggleWrapper/ToggleWrapper.tsx, packages/react/src/components/FilePanel/DefaultTabs/EmbedTab.tsx, packages/react/src/components/FilePanel/DefaultTabs/UploadTab.tsx
React components handle missing blocks with early returns or nullable child counts. Embed updates use props.blockId.
Markdown rebase block validation
packages/xl-ai/src/api/formats/markdown-blocks/tools/rebaseTool.ts
Markdown rebasing throws Error("block not found") when the requested block is absent.

Estimated code review effort: 2 (Simple) | ~15 minutes

Possibly related PRs

Poem

A rabbit checks each block with care,
No stale IDs remain unaware.
Toggles pause when blocks depart,
Uploads stop before they start.
Markdown reports the missing tale:
“Block not found,” and guards prevail.

🚥 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 The title clearly and concisely describes the primary change: guarding getBlock() calls to prevent stale-block TypeErrors.
Description check ✅ Passed The description covers the summary, rationale, changes, impact, testing, checklist, and issue reference; omitted tests and documentation are explained.
Linked Issues check ✅ Passed The changes satisfy issue [#2907] by guarding all identified ToggleWrapper getBlock() calls and safely exiting for stale blocks.
Out of Scope Changes check ✅ Passed The additional guards address the same stale-block failure mode and support the stated objective, so no unrelated changes are evident.
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-2907

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration.


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.

@pkg-pr-new

pkg-pr-new Bot commented Aug 4, 2026

Copy link
Copy Markdown

Open in StackBlitz

@blocknote/ariakit

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

@blocknote/code-block

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

@blocknote/core

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

@blocknote/mantine

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

@blocknote/react

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

@blocknote/server-util

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

@blocknote/shadcn

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

@blocknote/xl-ai

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

@blocknote/xl-docx-exporter

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

@blocknote/xl-email-exporter

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

@blocknote/xl-multi-column

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

@blocknote/xl-odt-exporter

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

@blocknote/xl-pdf-exporter

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

commit: 12e11aa

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-08-04 09:53 UTC

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 3

🧹 Nitpick comments (1)
packages/core/src/extensions/TableHandles/TableHandles.ts (1)

536-536: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Avoid assigning the nullable lookup through as any.

as any suppresses TypeScript checks at the stale-block boundary. Store the lookup in a local nullable variable, run the existing absence and type checks on that variable, and assign this.state.block only after those checks. Use a specific table-block type if a cast is still required.

Suggested type-safe shape
-    this.state.block = this.editor.getBlock(this.state.block.id) as any;
+    const currentBlock = this.editor.getBlock(this.state.block.id);
     if (
-      !this.state.block ||
-      this.state.block.type !== "table" ||
+      !currentBlock ||
+      currentBlock.type !== "table" ||
       !this.tableElement?.isConnected
     ) {
       // existing cleanup
     }
+    this.state.block = currentBlock;

As per coding guidelines, use vp run lint for linting and type-checking.

🤖 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/core/src/extensions/TableHandles/TableHandles.ts` at line 536,
Update the block refresh logic in the relevant TableHandles method to store
getBlock(this.state.block.id) in a nullable local variable instead of assigning
through as any. Apply the existing missing-block and type checks to that
variable, then assign this.state.block only after validation, using the specific
table-block type if a cast remains necessary; verify with vp run lint.

Source: Coding guidelines

🤖 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/core/src/extensions/SideMenu/SideMenu.ts`:
- Around line 244-249: Update the missing-block branch in the side-menu hover
handling around getBlock to hide the menu and emit the existing state before
returning. Clear or disable the stale visible state and remove the old
state.block reference so later same-block handling and side-menu actions cannot
reuse the removed block.

In `@packages/react/src/components/FilePanel/DefaultTabs/EmbedTab.tsx`:
- Around line 44-66: The update paths in EmbedTab must re-check block existence
immediately before each editor.updateBlock call using
editor.getBlock(props.blockId), and skip the update when the target was removed.
Apply the same guard in UploadTab.tsx at lines 79-82, ensuring a missing block
does not mark the upload as failed when the continuation is only ignoring a
removed target.

In `@packages/react/src/components/FilePanel/DefaultTabs/UploadTab.tsx`:
- Around line 79-82: Update the asynchronous upload continuation around
editor.uploadFile to re-check the block ID after the upload resolves, and call
editor.updateBlock only when the block still exists. Preserve the existing
render-time !block guard and finally block that clears loading state.

---

Nitpick comments:
In `@packages/core/src/extensions/TableHandles/TableHandles.ts`:
- Line 536: Update the block refresh logic in the relevant TableHandles method
to store getBlock(this.state.block.id) in a nullable local variable instead of
assigning through as any. Apply the existing missing-block and type checks to
that variable, then assign this.state.block only after validation, using the
specific table-block type if a cast remains necessary; verify with vp run lint.
🪄 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 Plus

Run ID: 9489547f-1d05-4b9b-850b-9ba85167dad5

📥 Commits

Reviewing files that changed from the base of the PR and between 3d08064 and 9dd7fd9.

📒 Files selected for processing (8)
  • packages/core/src/api/clipboard/fromClipboard/handleFileInsertion.ts
  • packages/core/src/blocks/ToggleWrapper/createToggleWrapper.ts
  • packages/core/src/extensions/SideMenu/SideMenu.ts
  • packages/core/src/extensions/TableHandles/TableHandles.ts
  • packages/react/src/blocks/ToggleWrapper/ToggleWrapper.tsx
  • packages/react/src/components/FilePanel/DefaultTabs/EmbedTab.tsx
  • packages/react/src/components/FilePanel/DefaultTabs/UploadTab.tsx
  • packages/xl-ai/src/api/formats/markdown-blocks/tools/rebaseTool.ts

Comment thread packages/core/src/extensions/SideMenu/SideMenu.ts
Comment thread packages/react/src/components/FilePanel/DefaultTabs/EmbedTab.tsx
Comment thread packages/react/src/components/FilePanel/DefaultTabs/UploadTab.tsx
…fety

- SideMenu: hide menu and clear hoveredBlock when getBlock returns
  undefined, preventing stale visible state
- EmbedTab: re-check block existence before each updateBlock call
- UploadTab: re-check block existence after async upload resolves
- TableHandles: use typed local variable instead of as any cast
@nperez0111
nperez0111 merged commit c32f968 into main Aug 4, 2026
27 of 28 checks passed
@nperez0111
nperez0111 deleted the issue-2907 branch August 4, 2026 09:52
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.

ToggleWrapper: uncaught TypeError reading 'id' from unguarded editor.getBlock(block)!

1 participant