Skip to content

fix(react): keep the suggestion menu reference while the menu does not move - #3249

Open
safaweslati wants to merge 1 commit into
TypeCellOS:mainfrom
safaweslati:fix/suggestion-menu-stable-reference
Open

safaweslati wants to merge 1 commit into
TypeCellOS:mainfrom
safaweslati:fix/suggestion-menu-stable-reference

Conversation

@safaweslati

@safaweslati safaweslati commented Oct 23, 2026 •

Copy link
Copy Markdown

Summary

Fast typing while a suggestion menu trigger is active can crash a React app with error 185 ("Maximum update depth exceeded"). This PR keeps the reference that SuggestionMenuController and GridSuggestionMenuController give to GenericPopover stable while the menu does not move.

Rationale

Both controllers build reference with an inline useExtensionState selector. The selector returns a new object on every store update. Also, SuggestionMenuView writes referencePos with getBoundingClientRect().toJSON(), which is a new object on every update, also when the menu stays at the same place.

GenericPopover has an effect on props.reference that calls refs.setPositionReference(...). That is a state update in Floating UI. So each keystroke adds one more update that runs after the commit.

When keys arrive back to back, the browser handles the input before React gets to that update. Each sync commit then finishes with the update still waiting, and React counts it as a nested update. After about 50 keys, React throws error 185. It is not an infinite loop. It is a counter that the queued updates fill.

We see this in production. A user types / (or another trigger character) and then a sentence, quickly, on a slow machine. We reproduce it with Playwright and with Chrome DevTools CPU throttling at human speeds: 4x slower CPU at 100 ms per key, and 6x slower CPU at 150 ms per key.

Changes

  • SuggestionMenuController and GridSuggestionMenuController build reference with useMemo. The memo uses the editor element and the x, y, width and height values of referencePos, not the object identity.
  • getBoundingClientRect builds a DOMRect from those values, so it never returns a stale rectangle.
  • A new test, SuggestionMenuController.reference.test.tsx, opens each menu, types a query and checks that GenericPopover gets one reference for the whole query.

Impact

When the anchor moves (the query wraps to a new line, or the editor scrolls), the values change, so the popover gets a new reference and moves as before. When the anchor does not move, the popover no longer sets its position reference again on each keystroke.

Testing

  • The new test fails on main (5 references for 5 keys, in both controllers) and passes with the change (1 reference).
  • pnpm test in packages/react passes.
  • tsc --noEmit in packages/react passes. Lint and the formatter pass on the changed files. On Windows I could not run the type-aware lint step (tsgolint does not start), so CI is the first run of that step.
  • Manual: we applied the same change as a pnpm patch to @blocknote/react@0.55.0 in our app. In a production build, fast typing after / and [ gave no error in 7 of 7 runs. Before the change, the same input crashed in 2 of 3 runs. The two throttled human speeds above also gave no error.

Screenshots/Video

N/A. No visual change.

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

None.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Improved positioning behavior for list and grid suggestion menus by basing their popover placement on the current editor position.
    • Kept menu placement consistent while users type, avoiding unnecessary changes to the menu’s positioning reference.

…t move

SuggestionMenuController and GridSuggestionMenuController built the
popover reference with an inline selector, so it was a new object on
every store update. GenericPopover then set its position reference
again on each keystroke. With fast typing, React counted these queued
updates as nested updates and threw error 185 after about 50 keys.

The reference now changes only with the editor element and the values
of referencePos.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@vercel

vercel Bot commented Oct 23, 2026

Copy link
Copy Markdown

@safaweslati is attempting to deploy a commit to the TypeCell Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: dbcc8ed3-723b-4d93-bfac-e7dd92f252bd
📥 Commits

Reviewing files that changed from the base of the PR and between 3dc9c7e and ba9372f.

📒 Files selected for processing: 3
  • packages/react/src/components/SuggestionMenu/GridSuggestionMenu/GridSuggestionMenuController.tsx
  • packages/react/src/components/SuggestionMenu/SuggestionMenuController.reference.test.tsx
  • packages/react/src/components/SuggestionMenu/SuggestionMenuController.tsx

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.

📝 Walkthrough

Both suggestion-menu controllers now memoize popover references from the editor element and position coordinates. Tests check that list and grid menu references retain the same identity across typing transactions.

Changes: Suggestion menu reference stability

Layer / File(s) Summary
Memoized references and identity tests
packages/react/src/components/SuggestionMenu/SuggestionMenuController.tsx, packages/react/src/components/SuggestionMenu/GridSuggestionMenu/GridSuggestionMenuController.tsx, packages/react/src/components/SuggestionMenu/SuggestionMenuController.reference.test.tsx
Both controllers build memoized references from the editor’s first child and position coordinates. The tests check reference identity after typing following / and : triggers.

Priority: ➖ Normal

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

Change: Bug fix

Suggested reviewers: nperez0111, matthewlipski, yousefed

Merge Risk: ⚪ Minimal · up to ba937

The reference-stability change is ready to merge after normal checks; no actionable issue remains.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 5 functions across 3 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Title check Passed The title names the fix (keeping the suggestion menu reference stable while the menu does not move) and matches the main change in SuggestionMenuController and GridSuggestionMenuController, which memo…
Description check Passed The description follows the repository template: it has Summary, Rationale, Changes, Impact, Testing, Screenshots/Video, Checklist and Additional Notes. It explains the root cause (unstable reference …
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Title check

Explanation

The title names the fix (keeping the suggestion menu reference stable while the menu does not move) and matches the main change in SuggestionMenuController and GridSuggestionMenuController, which memoize the popover reference. It is specific and follows the conventional-commit style. It does not name the crash (React error 185) but that is not required.

Full details: Description check

Explanation

The description follows the repository template: it has Summary, Rationale, Changes, Impact, Testing, Screenshots/Video, Checklist and Additional Notes. It explains the root cause (unstable reference from useExtensionState and getBoundingClientRect().toJSON(), with GenericPopover's effect causing nested updates and error 185), lists the changes, and describes the new test and manual verification. The Documentation checklist item is unchecked and not explained, and Additional Notes says None. These are minor gaps and do not block the review.

✨ Finishing Touches: 💡 1
🛠️ Fix failing CI checks: 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

A rabbit taps / with care,
Then : and query fill the air.
The menu reference stays in place,
Through each small typing change of pace.
The bunny hops, pleased with the trace.

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

This branch has not been deployed

No deployments
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.

1 participant