{"record":{"id":"0f608bbc1ae695a0","repo":"ianstormtaylor/slate","slug":"failed-to-attach-mutationobserver-node-is-undef-0f608b","errorCode":null,"errorMessage":"Failed to attach MutationObserver, `node` is undefined","messagePattern":"Failed to attach MutationObserver, `node` is undefined","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/slate-react/src/components/restore-dom/restore-dom.tsx","lineNumber":40,"sourceCode":"type RestoreDOMProps = {\n  children?: ReactNode\n  receivedUserInput: RefObject<boolean>\n  node: RefObject<HTMLDivElement>\n}\n\n// We have to use a class component here since we rely on `getSnapshotBeforeUpdate` which has no FC equivalent\n// to run code synchronously immediately before react commits the component update to the DOM.\nclass RestoreDOMComponent extends Component<RestoreDOMProps> {\n  static contextType = EditorContext\n  context: ContextType<typeof EditorContext> = null\n\n  private manager: RestoreDOMManager | null = null\n  private mutationObserver: MutationObserver | null = null\n\n  observe() {\n    const { node } = this.props\n    if (!node.current) {\n      throw new Error('Failed to attach MutationObserver, `node` is undefined')\n    }\n\n    this.mutationObserver?.observe(node.current, MUTATION_OBSERVER_CONFIG)\n  }\n\n  componentDidMount() {\n    const { receivedUserInput } = this.props\n    const editor = this.context!\n\n    this.manager = createRestoreDomManager(editor, receivedUserInput)\n    this.mutationObserver = new MutationObserver(this.manager.registerMutations)\n\n    this.observe()\n  }\n\n  getSnapshotBeforeUpdate() {\n    const pendingMutations = this.mutationObserver?.takeRecords()\n    if (pendingMutations?.length) {","sourceCodeStart":22,"sourceCodeEnd":58,"githubUrl":"https://github.com/ianstormtaylor/slate/blob/72a37c701e5da4bb13a305d416d9c070d19d3a45/packages/slate-react/src/components/restore-dom/restore-dom.tsx#L22-L58","documentation":"RestoreDOM's MutationObserver wrapper throws when observe() is called before the restored DOM node ref exists (node.current is undefined). The observer must attach to the actual DOM element rendered by the <Slate> component; if the ref is not populated yet, restoration of DOM state cannot work.","triggerScenarios":"Rendering <RestoreDOM> without a valid node ref (or with a ref that is null on mount); using SlateReact internals outside the editable component; SSR where refs never populate; version mismatch between slate-react internals after upgrades.","commonSituations":"Custom editable components that reuse restore-dom.tsx with wrong props; upgrading slate-react across versions where RestoreDOM props changed; rendering the editor into a detached/hidden container where the ref is unset when observe runs.","solutions":["Ensure the ref passed to RestoreDOM is attached to a rendered element before observe() runs (pass the same ref used by the editable component)","Avoid consuming internal restore-dom components directly; use the shipped <Editable>/ <Slate> components","If on a custom setup, upgrade slate-react to a matching version so internal component contracts line up"],"exampleFix":"// before\n<RestoreDOM node={{ current: null }} ... />\n\n// after\nconst ref = useRef<HTMLDivElement>(null)\n// attach ref to the rendered editable element\n<RestoreDOM node={ref} ... />","handlingStrategy":"validation","validationCode":"useEffect(() => {\n  if (!node.current) return\n  observe()\n}, [node.current])","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Attach refs to actually rendered elements before observing","Don't consume slate-react internal components (restore-dom) directly in app code","Keep slate and slate-react versions in sync to avoid internal contract drift"],"tags":["mutation-observer","refs","restore-dom","react","ssr"],"backgroundTag":"react-ref-not-attached","analyzedSha":"72a37c701e5da4bb13a305d416d9c070d19d3a45","analyzedAt":"2026-08-27T23:04:51.145Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}