Skip to content

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

Description

@jhuurtado

Summary

createToggleWrapper.ts calls editor.getBlock(block)! (non-null assertion) in four places. When getBlock returns undefined — a stale closed-over block whose node is no longer in the document — the very next thing that runs is `toggle-${block.id}` inside defaultToggledState.set, which throws.

Because these run inside raw DOM addEventListener handlers, the error is uncaught: it escapes React error boundaries and lands in window.onerror.

This is the same class of bug as #2847 / #2821 (TableHandles.ts), but in a different file, and it is not fixed by that change.

Version

  • Reproduced in production on @blocknote/core 0.49.0 and 0.52.1.
  • packages/core/src/blocks/ToggleWrapper/createToggleWrapper.ts is byte-identical between v0.49.0 and v0.52.1.
  • Still present on main at the time of writing (4 unguarded getBlock(block)! calls).

Where

https://github.com/TypeCellOS/BlockNote/blob/v0.52.1/packages/core/src/blocks/ToggleWrapper/createToggleWrapper.ts

// defaultToggledState.set — line 12
set: (block, isToggled: boolean) =>
  window.localStorage.setItem(
    `toggle-${block.id}`,          // <-- throws if `block` is undefined
    isToggled ? "true" : "false",
  ),

Unguarded call sites: lines 55, 62, 114, 127. For example, in toggleButtonOnClick:

toggleWrapper.setAttribute("data-show-children", "true");
toggledState.set(editor.getBlock(block)!, true);   // line 62 — unguarded

if (
  editor.isEditable &&
  editor.getBlock(block)?.children.length === 0 &&   // line 66 — guarded
  !dom.contains(toggleAddBlockButton)
) {

Note the inconsistency: within the same function, getBlock(block) is optional-chained on line 66 and 108 but non-null-asserted on lines 55, 62, 114 and 127. That asymmetry suggests the ! is an oversight rather than an invariant the code can rely on.

Symptom

Uncaught TypeError, wording depending on the engine:

  • Chrome/V8 — Cannot read properties of undefined (reading 'id')
  • Firefox/SpiderMonkey — can't access property "id", e is undefined

Sentry reports it with mechanism.type: auto.browser.browserapierrors.addEventListener and handled: false. The breadcrumb immediately before the throw is a click on div.bn-toggle-wrapper > button.bn-toggle-button.

Reproduction

I have not found reliable manual steps — same situation as the reporter of #2847, who also found their case through production error monitoring rather than a manual repro.

What the data shows: it fires on documents containing toggle blocks, on both Chrome and Firefox, clustered around interactions with the toggle button and the block menus. The plausible path is that block is captured in the closure when the wrapper is created, the underlying node is later replaced or removed (a replaceBlocks, a collaborative/programmatic update, or a menu action that recreates the block), and a subsequent click on the still-mounted toggle button resolves getBlock to undefined.

Editor config, in case it helps narrow it down: schema with toggleListItem enabled alongside paragraph, heading, bulletListItem, numberedListItem, checkListItem, quote, codeBlock, image and table; @blocknote/mantine; no collaboration/Yjs. Content is loaded with replaceBlocks after parsing HTML or Markdown.

Impact for us is low — the editor keeps working and autosave still succeeds — but the errors are unhandled, so they surface as crashes in error reporting and are indistinguishable from real ones.

Suggested fix

Mirror what #2821 did for the table handles: bail out instead of asserting.

const currentBlock = editor.getBlock(block);
if (currentBlock) {
  toggledState.set(currentBlock, false);
}

Making ToggledState["set"]/["get"] tolerate undefined would also work, but guarding at the call sites keeps the contract clearer and matches the existing optional chaining a few lines below.

Happy to open a PR if that would help.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions