{"record":{"id":"850a549fabc5073d","repo":"ianstormtaylor/slate","slug":"failed-to-attach-mutationobserver-node-is-undef","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/hooks/use-mutation-observer.ts","lineNumber":19,"sourceCode":"import { RefObject, useEffect, useState } from 'react'\nimport { useIsomorphicLayoutEffect } from './use-isomorphic-layout-effect'\n\nexport function useMutationObserver(\n  node: RefObject<HTMLElement>,\n  callback: MutationCallback,\n  options: MutationObserverInit\n) {\n  const [mutationObserver] = useState(() => new MutationObserver(callback))\n\n  useIsomorphicLayoutEffect(() => {\n    // Discard mutations caused during render phase. This works due to react calling\n    // useLayoutEffect synchronously after the render phase before the next tick.\n    mutationObserver.takeRecords()\n  })\n\n  useEffect(() => {\n    if (!node.current) {\n      throw new Error('Failed to attach MutationObserver, `node` is undefined')\n    }\n\n    mutationObserver.observe(node.current, options)\n    return () => mutationObserver.disconnect()\n  }, [mutationObserver, node, options])\n}\n","sourceCodeStart":1,"sourceCodeEnd":26,"githubUrl":"https://github.com/ianstormtaylor/slate/blob/72a37c701e5da4bb13a305d416d9c070d19d3a45/packages/slate-react/src/hooks/use-mutation-observer.ts#L1-L26","documentation":"useMutationObserver (used internally by slate-react's Android input manager) attaches a MutationObserver to a ref's current node in an effect. If node.current is still undefined when the effect runs, the observer cannot be attached, so the hook throws. This typically means the ref was never attached to a rendered DOM node or the node unmounted before the effect.","triggerScenarios":"Passing a ref object to useMutationObserver whose .current is null/undefined at effect time (node not yet mounted, component returned null, or the ref was never forwarded to a DOM element); SSR where refs never populate.","commonSituations":"Using useAndroidInputManager or useMutationObserver with a conditionally-rendered element; forgetting to forward the ref in a wrapped component; React 18 strict-mode timing changes on Android-focused code paths.","solutions":["Ensure the ref is attached to a DOM element that is always rendered before the effect runs (forward the ref through any wrapper components)","If the node renders conditionally, guard the hook usage so the observer component only mounts when the node exists","Verify you're not calling the hook during SSR — only attach after mount"],"exampleFix":"// before\nconst ref = useRef<HTMLDivElement>(null)\nuseMutationObserver(ref, callback, options) // ref.current still null\nreturn cond && <div ref={ref} />\n\n// after\nreturn <div ref={ref}>{cond && <Child />}</div>\n// node always mounted; observer attaches safely","handlingStrategy":"validation","validationCode":"if (node.current != null) {\n  useMutationObserverEffect(node, cb, options)\n}","typeGuard":"const isRefAttached = (ref) => ref.current != null","tryCatchPattern":null,"preventionTips":["Always attach the ref to an unconditionally rendered DOM element","Forward refs through wrapper components","Never call observer hooks during SSR"],"tags":["slate-react","mutation-observer","refs","android","mount"],"backgroundTag":"ref-not-attached","analyzedSha":"72a37c701e5da4bb13a305d416d9c070d19d3a45","analyzedAt":"2026-08-27T23:04:51.145Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}