fix(react): prevent useEditorState selectors from running on stale editor#7586
fix(react): prevent useEditorState selectors from running on stale editor#7586omar-y-abdi wants to merge 1 commit intoueberdosis:mainfrom
Conversation
…itor When `useEditor` deps change, the old editor is destroyed and a new one is created. The `EditorStateManager` in `useEditorState` only updated its editor reference via a layout effect (`watch`), which fires after render. This caused `useSyncExternalStoreWithSelector` to call selectors with a snapshot containing the old, destroyed editor — leading to errors like "The editor view is not available". Fix: eagerly sync the editor reference during render via a new `setEditorInstance` method, called before the selector runs. The layout effect still handles transaction listener setup/teardown. Fixes ueberdosis#7346
✅ Deploy Preview for tiptap-embed ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
|
There was a problem hiding this comment.
Pull request overview
Fixes a React hook edge case where useEditorState selectors could run against a stale/destroyed editor instance during useEditor re-creation, by ensuring the EditorStateManager’s editor reference is synchronized before useSyncExternalStoreWithSelector reads the snapshot.
Changes:
- Added
EditorStateManager.setEditorInstance()to eagerly sync the current editor reference during render (and bump the snapshot version). - Called
setEditorInstance()inuseEditorStateprior touseSyncExternalStoreWithSelectorto avoid stale snapshots. - Added unit tests covering editor instance swaps (including null ↔ instance) and ensuring selectors don’t run on a destroyed editor.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| packages/react/src/useEditorState.ts | Eagerly updates the stored editor reference during render so getSnapshot() cannot return a stale/destroyed editor when the instance changes. |
| packages/react/src/tests/useEditorState.test.ts | Adds hook-level regression tests reproducing the destroyed-editor selector scenario and related editor lifecycle transitions. |
You can also share your feedback on Copilot code review. Take the survey.
Summary
Fixes #7346
When
useEditordeps change, the old editor is destroyed and a new one is created.EditorStateManagerinuseEditorStateonly updated its editor reference via a layout effect (watch), which fires after render. This causeduseSyncExternalStoreWithSelectorto call selectors with a snapshot containing the old, destroyed editor — leading to:Root Cause
The lifecycle ordering when deps change:
useEditoreffect runs →refreshEditorInstancedestroys old editor, creates new onesetEditornotifiesuseSyncExternalStore→ React re-rendersuseEditorState'suseSyncExternalStoreWithSelectorcallsgetSnapshot()EditorStateManagerstill holds the old (destroyed) editor —watchhasn't fired yet (layout effect)Fix
Added
setEditorInstance()toEditorStateManagerthat eagerly syncs the editor reference during render, beforeuseSyncExternalStoreWithSelectorreads the snapshot. The layout effect (watch) still handles transaction listener setup/teardown as before.The change is 2 additions:
setEditorInstancemethod onEditorStateManager(7 lines)useEditorStatebefore the store selector runs (1 line + comments)Testing
packages/react/src/__tests__/useEditorState.test.ts