[{"data":1,"prerenderedAt":135},["ShallowReactive",2],{"qa-\u002Freact\u002Fpatterns\u002Fportals-refs":3},{"page":4,"siblings":115,"blog":132},{"id":5,"title":6,"body":7,"description":11,"difficulty":14,"extension":15,"framework":16,"frameworkSlug":17,"meta":18,"navigation":19,"order":20,"path":21,"questions":22,"questionsCount":105,"related":106,"seo":107,"seoDescription":108,"stem":109,"subtopic":110,"topic":111,"topicSlug":112,"updated":113,"__hash__":114},"qa\u002Freact\u002Fpatterns\u002Fportals-refs.md","Portals Refs",{"type":8,"value":9,"toc":10},"minimark",[],{"title":11,"searchDepth":12,"depth":12,"links":13},"",2,[],"medium","md","React","react",{},true,4,"\u002Freact\u002Fpatterns\u002Fportals-refs",[23,28,32,36,40,44,48,53,57,61,65,69,73,77,81,85,89,93,97,101],{"id":24,"difficulty":25,"q":26,"a":27},"what-is-createportal","easy","What does ReactDOM.createPortal do and when should you use it?","`ReactDOM.createPortal(children, domNode)` renders `children` into a **different\nDOM node** than the component's parent, while keeping them inside the **React\ncomponent tree**. The portal children still receive context, state, and events\nfrom their React parent — only the DOM placement changes.\n\n```jsx\nimport { createPortal } from 'react-dom'\n\nfunction Modal({ children, onClose }) {\n  return createPortal(\n    \u002F\u002F JSX rendered here...\n    \u003Cdiv className=\"modal-overlay\" onClick={onClose}>\n      \u003Cdiv className=\"modal-content\" onClick={e => e.stopPropagation()}>\n        {children}\n      \u003C\u002Fdiv>\n    \u003C\u002Fdiv>,\n    \u002F\u002F ...but mounted into this DOM node, outside the app root\n    document.getElementById('modal-root')\n  )\n}\n```\n\nUse portals when you need a component to **escape the CSS stacking context** of\nits parent — modals, tooltips, dropdown menus, toasts, and popovers all commonly\nneed to render at the `\u003Cbody>` level to avoid `overflow: hidden` or `z-index`\nclipping from ancestor elements.\n\n**Rule of thumb:** Reach for `createPortal` any time a UI element needs to visually\nfloat above the rest of the page, regardless of where it lives in the component tree.\n",{"id":29,"difficulty":14,"q":30,"a":31},"portal-react-tree-vs-dom-tree","A portal renders its children in a different DOM node. Does that affect React context or event propagation?","No — portals are a **DOM-only escape hatch**. React's virtual component tree is\nunchanged, so everything that flows through the React tree — **context values,\nstate, refs, and synthetic events** — still works as if the portal were rendered\ninline.\n\n```jsx\nfunction App() {\n  const [theme] = useContext(ThemeContext) \u002F\u002F 'dark'\n\n  return (\n    \u003CThemeContext.Provider value=\"dark\">\n      \u003CModal>\n        {\u002F* ThemeContext reads 'dark' even though Modal\n            is mounted under document.body in the DOM *\u002F}\n        \u003CThemedButton \u002F>\n      \u003C\u002FModal>\n    \u003C\u002FThemeContext.Provider>\n  )\n}\n```\n\nThis means a portal component can consume any context provided by its React\nancestors, even if those ancestors are DOM siblings or even DOM ancestors of the\nportal's mount point.\n\n**Rule of thumb:** Think of portals as a teleport for DOM placement only — the\nReact component lineage is unaffected, so all React features keep working normally.\n",{"id":33,"difficulty":14,"q":34,"a":35},"portal-event-bubbling-gotcha","How do synthetic events bubble through a portal? Why is this a common interview gotcha?","Synthetic events inside a portal bubble **through the React component tree, not\nthe DOM tree**. A click inside a modal portal bubbles up to the React ancestor\nthat rendered the portal, even though the modal's DOM node is a sibling of\n`#root` in the actual DOM.\n\n```jsx\nfunction Page() {\n  \u002F\u002F This handler fires when the button inside Modal is clicked,\n  \u002F\u002F because the portal's React parent is Page — even though\n  \u002F\u002F the modal DOM is outside #root entirely.\n  const handleClick = () => console.log('caught in Page')\n\n  return (\n    \u003Cdiv onClick={handleClick}>\n      \u003CModal>\n        \u003Cbutton>Click me\u003C\u002Fbutton> {\u002F* bubbles to Page via React tree *\u002F}\n      \u003C\u002FModal>\n    \u003C\u002Fdiv>\n  )\n}\n```\n\nThe gotcha: if you add a `document.addEventListener('click', ...)` outside React\nand expect portal clicks to bubble through the DOM, you'll see them — but a React\n`onClick` placed on a DOM ancestor of `#root` will also fire because the native\nevent does travel through the real DOM too. The **React synthetic event** system\nis what bubbles through the React tree.\n\n**Rule of thumb:** Always think of event bubbling in terms of the React component\ntree, not the rendered DOM tree — portals don't change the React ancestry.\n",{"id":37,"difficulty":25,"q":38,"a":39},"portal-modal-z-index-escape","Why do modals rendered inside a deeply nested component often break z-index stacking, and how does createPortal fix it?","CSS `z-index` is scoped to a **stacking context**. An ancestor with\n`transform`, `opacity \u003C 1`, `position: relative\u002Fabsolute` and a `z-index`, or\n`overflow: hidden` creates a new stacking context that caps the `z-index` of all\nits descendants. A modal with `z-index: 9999` is still trapped below an ancestor\nwith `z-index: 1` if that ancestor forms its own stacking context.\n\n```jsx\n\u002F\u002F Without portal — modal trapped inside stacking context of .card\n\u002F\u002F \u003Cdiv class=\"card\" style=\"transform: translateZ(0); z-index: 1\">\n\u002F\u002F   \u003CModal \u002F>   ← z-index: 9999 but still below sibling stacking contexts\n\u002F\u002F \u003C\u002Fdiv>\n\n\u002F\u002F With portal — modal escapes to body, outside all stacking contexts\nfunction Modal({ children }) {\n  return createPortal(\n    \u003Cdiv className=\"modal\" style={{ zIndex: 9999 }}>\n      {children}\n    \u003C\u002Fdiv>,\n    document.body \u002F\u002F top-level stacking context\n  )\n}\n```\n\nPortaling to `document.body` (or a dedicated `#modal-root` sibling of `#root`)\nremoves the modal from all ancestor stacking contexts and lets it freely float\nabove every other element.\n\n**Rule of thumb:** If a modal or tooltip is clipped or covered despite a high\n`z-index`, the component tree has a stacking context trap — use `createPortal`\nto escape it.\n",{"id":41,"difficulty":14,"q":42,"a":43},"portal-accessibility-focus","How should you handle focus management and accessibility for a portal-based modal?","A modal portal must manage **focus trap**, **aria attributes**, and **keyboard\ndismissal** explicitly, because rendering outside the normal DOM flow doesn't\nautomatically handle any of these.\n\n```jsx\nimport { useEffect, useRef } from 'react'\nimport { createPortal } from 'react-dom'\n\nfunction Modal({ isOpen, onClose, children }) {\n  const dialogRef = useRef(null)\n\n  useEffect(() => {\n    if (!isOpen) return\n    \u002F\u002F Move focus into the modal when it opens\n    dialogRef.current?.focus()\n    \u002F\u002F Close on Escape\n    const onKey = (e) => e.key === 'Escape' && onClose()\n    document.addEventListener('keydown', onKey)\n    return () => document.removeEventListener('keydown', onKey)\n  }, [isOpen, onClose])\n\n  if (!isOpen) return null\n\n  return createPortal(\n    \u003Cdiv\n      role=\"dialog\"\n      aria-modal=\"true\"\n      tabIndex={-1}  \u002F\u002F makes div focusable\n      ref={dialogRef}\n      className=\"modal\"\n    >\n      {children}\n    \u003C\u002Fdiv>,\n    document.body\n  )\n}\n```\n\nKey requirements: `role=\"dialog\"`, `aria-modal=\"true\"` (tells screen readers\nto ignore background content), focus moved into the dialog on open, and Escape\nkey closes it. Libraries like `focus-trap-react` automate the focus-trap cycle.\n\n**Rule of thumb:** A portal gives you DOM freedom, but accessibility is still\nyour responsibility — always add `role=\"dialog\"`, `aria-modal`, and focus management.\n",{"id":45,"difficulty":14,"q":46,"a":47},"portal-cleanup","How does React clean up a portal when the component unmounts?","React automatically removes portal content from the DOM when the component\nthat rendered the portal unmounts. You do not need to manually call\n`ReactDOM.unmountComponentAtNode` for standard portals.\n\n```jsx\nfunction App() {\n  const [showModal, setShowModal] = useState(false)\n\n  return (\n    \u003C>\n      \u003Cbutton onClick={() => setShowModal(true)}>Open\u003C\u002Fbutton>\n      {\u002F* When showModal becomes false, React unmounts Modal and\n          removes its portal DOM from document.body automatically *\u002F}\n      {showModal && (\n        \u003CModal onClose={() => setShowModal(false)}>\n          \u003Cp>Content\u003C\u002Fp>\n        \u003C\u002FModal>\n      )}\n    \u003C\u002F>\n  )\n}\n```\n\nIf you **dynamically created the mount node** yourself (e.g. with\n`document.createElement('div')` and `appendChild`), you are responsible for\nremoving that container element in a cleanup function or `useEffect` return.\nThe portal content inside it is still cleaned by React; the container node\nitself is your concern.\n\n**Rule of thumb:** Let React manage unmounting; only clean up manually if you\ncreated the portal's host DOM element dynamically outside of React.\n",{"id":49,"difficulty":50,"q":51,"a":52},"portal-testing","hard","How do you test a component that uses createPortal?","Jest + React Testing Library (RTL) works with portals out of the box because\nRTL renders into `document.body` by default and `createPortal` also targets\n`document.body` — so portal content is in `document.body` and queryable normally.\n\n```jsx\n\u002F\u002F Modal.test.jsx\nimport { render, screen } from '@testing-library\u002Freact'\nimport userEvent from '@testing-library\u002Fuser-event'\nimport Modal from '.\u002FModal'\n\ntest('renders portal content in document.body', () => {\n  render(\u003CModal isOpen onClose={() => {}}>Hello portal\u003C\u002FModal>)\n  \u002F\u002F Even though content is in a portal, RTL queries search document.body\n  expect(screen.getByText('Hello portal')).toBeInTheDocument()\n})\n\ntest('calls onClose on Escape key', async () => {\n  const onClose = jest.fn()\n  render(\u003CModal isOpen onClose={onClose}>Content\u003C\u002FModal>)\n  await userEvent.keyboard('{Escape}')\n  expect(onClose).toHaveBeenCalled()\n})\n```\n\nIf your portal targets a custom node (e.g. `document.getElementById('modal-root')`),\nadd `\u003Cdiv id=\"modal-root\" \u002F>` to `document.body` in a `beforeEach` and clean it\nup in `afterEach`.\n\n**Rule of thumb:** RTL's document-level query approach means portals require no\nspecial setup — only custom mount targets need a manual DOM fixture.\n",{"id":54,"difficulty":25,"q":55,"a":56},"what-is-useref","What does useRef return and what are its two main use cases?","`useRef(initialValue)` returns a **plain mutable object** `{ current: initialValue }`\nthat persists for the entire lifetime of the component. Mutating `.current` does\n**not** trigger a re-render.\n\n```jsx\n\u002F\u002F Use case 1: DOM access\nfunction TextInput() {\n  const inputRef = useRef(null)\n  const focus = () => inputRef.current.focus() \u002F\u002F imperative DOM call\n  return \u003Cinput ref={inputRef} \u002F>\n}\n\n\u002F\u002F Use case 2: mutable container (timer id, previous value, etc.)\nfunction Interval() {\n  const timerId = useRef(null)\n  const start = () => { timerId.current = setInterval(tick, 1000) }\n  const stop = () => clearInterval(timerId.current)\n  return \u003C>\u003Cbutton onClick={start}>Start\u003C\u002Fbutton>\u003Cbutton onClick={stop}>Stop\u003C\u002Fbutton>\u003C\u002F>\n}\n```\n\nThe two distinct jobs: (1) **hold a reference to a DOM element** so you can call\nimperative DOM APIs (focus, scroll, measure); (2) **store any mutable value** that\nmust survive re-renders without causing them — timers, intervals, previous state,\nanimation frame IDs, WebSocket instances.\n\n**Rule of thumb:** If you need to read or write a value across renders without\ntriggering one, `useRef` is the right tool — it's essentially instance state for\nfunction components.\n",{"id":58,"difficulty":14,"q":59,"a":60},"ref-vs-state","What is the key difference between a ref and state, and when would you choose one over the other?","The fundamental difference: **state changes schedule a re-render; ref mutations\ndo not**. Both persist across renders, but only state drives the UI.\n\n```jsx\nfunction Counter() {\n  const [count, setCount] = useState(0) \u002F\u002F changing → re-render\n  const renderCount = useRef(0)         \u002F\u002F changing → no re-render\n\n  useEffect(() => {\n    renderCount.current += 1 \u002F\u002F track silently, no render loop\n  })\n\n  return (\n    \u003Cdiv>\n      \u003Cp>Count: {count}\u003C\u002Fp>                       {\u002F* must be state *\u002F}\n      \u003Cp>Renders: {renderCount.current}\u003C\u002Fp>       {\u002F* stale between renders, but OK here *\u002F}\n      \u003Cbutton onClick={() => setCount(c => c + 1)}>+1\u003C\u002Fbutton>\n    \u003C\u002Fdiv>\n  )\n}\n```\n\nRef mutations are also **synchronous** — `ref.current` updates immediately with\nno batching. State updates may be batched and applied asynchronously.\n\nChoose **state** when the value needs to appear in the rendered output or drive\nconditional logic inside JSX. Choose **ref** when the value is used by event\nhandlers \u002F effects only and shouldn't re-render the component when it changes.\n\n**Rule of thumb:** If a value needs to be visible in the UI, it must be state;\nif it's behind-the-scenes bookkeeping, a ref keeps the render cycle clean.\n",{"id":62,"difficulty":14,"q":63,"a":64},"callback-ref","What is a callback ref and when is it more useful than useRef?","A **callback ref** is a function passed to the `ref` prop instead of a ref\nobject. React calls it with the DOM node when the component mounts, and with\n`null` when it unmounts.\n\n```jsx\nfunction MeasuredBox() {\n  const [height, setHeight] = useState(0)\n\n  \u002F\u002F Called once with the DOM node on mount, null on unmount\n  const measuredRef = useCallback((node) => {\n    if (node !== null) {\n      setHeight(node.getBoundingClientRect().height)\n    }\n  }, []) \u002F\u002F stable reference — no re-registration on every render\n\n  return (\n    \u003Cdiv ref={measuredRef}>\n      Height is {height}px\n    \u003C\u002Fdiv>\n  )\n}\n```\n\n`useRef` gives you the node after render but doesn't notify you when the node\nchanges. A callback ref is superior when you need to **react to the node being\nattached or detached** — measuring layout after conditional rendering, attaching\nthird-party libraries imperatively, or observing dynamic child nodes.\n\n**Rule of thumb:** Use `useRef` for stable, always-mounted nodes; use a callback\nref when you need to run code the moment the element appears or disappears in the DOM.\n",{"id":66,"difficulty":14,"q":67,"a":68},"ref-forwarding","What is ref forwarding and why is it needed for custom components?","By default, the `ref` prop on a custom component is **not forwarded** to the\nunderlying DOM element — React reserves `ref` and doesn't pass it through `props`.\n`React.forwardRef` opts a component into forwarding the ref to a child element.\n\n```jsx\n\u002F\u002F Without forwardRef: ref on \u003CFancyInput> points to nothing useful\n\u002F\u002F With forwardRef: ref reaches the real \u003Cinput>\nconst FancyInput = React.forwardRef((props, ref) => (\n  \u003Cinput\n    {...props}\n    ref={ref}              \u002F\u002F forwarded ref attached to DOM input\n    className=\"fancy\"\n  \u002F>\n))\n\n\u002F\u002F Parent can now focus the input imperatively\nfunction Form() {\n  const inputRef = useRef(null)\n  return (\n    \u003C>\n      \u003CFancyInput ref={inputRef} placeholder=\"Type here\" \u002F>\n      \u003Cbutton onClick={() => inputRef.current.focus()}>Focus\u003C\u002Fbutton>\n    \u003C\u002F>\n  )\n}\n```\n\nRef forwarding is common in **design-system \u002F component-library** components\n(buttons, inputs, modals) that wrap a native element but need to expose the DOM\nnode to consumers for accessibility or focus management.\n\n**Rule of thumb:** Any reusable component that wraps a DOM element should use\n`forwardRef` so library consumers retain imperative DOM access.\n",{"id":70,"difficulty":50,"q":71,"a":72},"useimperativehandle","What does useImperativeHandle do, and when should you use it?","`useImperativeHandle(ref, createHandle, deps)` lets a child component **control\nwhat the parent's ref exposes** — instead of exposing the raw DOM node, it\nexposes a custom object with only the methods the parent should call.\n\n```jsx\nconst VideoPlayer = React.forwardRef((props, ref) => {\n  const videoRef = useRef(null)\n\n  \u002F\u002F Parent only gets play\u002Fpause — not direct DOM access\n  useImperativeHandle(ref, () => ({\n    play: () => videoRef.current.play(),\n    pause: () => videoRef.current.pause(),\n  }), [])\n\n  return \u003Cvideo ref={videoRef} src={props.src} \u002F>\n})\n\nfunction App() {\n  const playerRef = useRef(null)\n  return (\n    \u003C>\n      \u003CVideoPlayer ref={playerRef} src=\"\u002Fclip.mp4\" \u002F>\n      \u003Cbutton onClick={() => playerRef.current.play()}>Play\u003C\u002Fbutton>\n    \u003C\u002F>\n  )\n}\n```\n\nThis is an **encapsulation tool**: it prevents parent components from reaching\ninto child internals and calling arbitrary DOM methods, keeping the API surface\nintentionally minimal.\n\n**Rule of thumb:** Prefer `useImperativeHandle` over raw `forwardRef` whenever\nyou need to expose imperative behavior from a child component — it enforces a\nclean, intentional API boundary.\n",{"id":74,"difficulty":14,"q":75,"a":76},"ref-mutable-container-previous-value","How do you use a ref to track the previous value of a prop or state variable?","Refs persist across renders without resetting, so they're perfect for storing\nthe **value from the last render** before state\u002Fprops update. The pattern is a\none-liner `useEffect` that writes `.current` after every render.\n\n```jsx\nfunction usePrevious(value) {\n  const ref = useRef(undefined)\n\n  useEffect(() => {\n    \u002F\u002F Runs after render — so during the current render, ref.current\n    \u002F\u002F still holds the value from the PREVIOUS render\n    ref.current = value\n  })\n\n  return ref.current \u002F\u002F previous render's value\n}\n\nfunction PriceDisplay({ price }) {\n  const prevPrice = usePrevious(price)\n  const direction = price > prevPrice ? '▲' : '▼'\n  return \u003Cspan>{price} {prevPrice !== undefined && direction}\u003C\u002Fspan>\n}\n```\n\nThis works because `useEffect` runs after the render is committed to the DOM.\nDuring the render that reads `ref.current`, the effect from the previous render\nhas already written the old value in, but the new value hasn't been written yet.\n\n**Rule of thumb:** Store previous values in a ref, not state — reading the\nprevious value shouldn't cause an extra re-render.\n",{"id":78,"difficulty":14,"q":79,"a":80},"ref-timeout-cleanup","Why should you store setTimeout or setInterval IDs in a ref rather than state?","Storing a timer ID in **state** would trigger a re-render every time you start\nor clear a timer, which is wasteful and can cause cascading effects. A **ref**\nstores it silently.\n\n```jsx\nfunction Debounced({ onSearch }) {\n  const timerRef = useRef(null)\n\n  const handleChange = (e) => {\n    \u002F\u002F Cancel previous timer without re-rendering\n    clearTimeout(timerRef.current)\n    timerRef.current = setTimeout(() => {\n      onSearch(e.target.value)\n    }, 300)\n  }\n\n  \u002F\u002F Clean up on unmount to prevent calling onSearch after component is gone\n  useEffect(() => {\n    return () => clearTimeout(timerRef.current)\n  }, [])\n\n  return \u003Cinput onChange={handleChange} \u002F>\n}\n```\n\nThe same pattern applies to `requestAnimationFrame` IDs, `setInterval` IDs,\nWebSocket instances, and Intersection\u002FResizeObserver instances — anything that\nneeds to be cancelled\u002Fdestroyed on unmount but doesn't affect the render output.\n\n**Rule of thumb:** Anything you'd cancel in cleanup belongs in a ref, not state —\ntimer IDs and resource handles are bookkeeping, not UI data.\n",{"id":82,"difficulty":14,"q":83,"a":84},"accessing-child-dom-from-parent","How do you access a child component's DOM node from a parent component?","The standard approach is `React.forwardRef` in the child, which allows the\nparent to pass a ref that the child attaches to its DOM element.\n\n```jsx\n\u002F\u002F Child forwards ref to its inner \u003Cdiv>\nconst Card = React.forwardRef(({ title, children }, ref) => (\n  \u003Cdiv ref={ref} className=\"card\">\n    \u003Ch2>{title}\u003C\u002Fh2>\n    {children}\n  \u003C\u002Fdiv>\n))\n\n\u002F\u002F Parent attaches ref and can read the DOM node\nfunction Page() {\n  const cardRef = useRef(null)\n\n  const scrollIntoView = () => {\n    cardRef.current?.scrollIntoView({ behavior: 'smooth' })\n  }\n\n  return (\n    \u003C>\n      \u003Cbutton onClick={scrollIntoView}>Scroll to card\u003C\u002Fbutton>\n      \u003CCard ref={cardRef} title=\"Target\">content\u003C\u002FCard>\n    \u003C\u002F>\n  )\n}\n```\n\nFor class components, a plain `ref` already points to the class instance, so\nyou can call public methods directly without `forwardRef`. For function\ncomponents, `forwardRef` + optionally `useImperativeHandle` is the only way.\n\n**Rule of thumb:** Always use `forwardRef` in any reusable function component\nthat wraps a DOM element — parents often need scroll, focus, or measurement access.\n",{"id":86,"difficulty":50,"q":87,"a":88},"refs-as-design-smell","When do refs indicate a design smell in a React component?","Refs are an **imperative escape hatch** — reaching for them frequently is a sign\nthe component is fighting React's declarative model instead of working with it.\n\n```jsx\n\u002F\u002F Smell: using ref to read input value instead of controlled state\nfunction BadForm() {\n  const inputRef = useRef(null)\n  const submit = () => console.log(inputRef.current.value) \u002F\u002F side-channel read\n  return \u003C>\u003Cinput ref={inputRef} \u002F>\u003Cbutton onClick={submit}>Send\u003C\u002Fbutton>\u003C\u002F>\n}\n\n\u002F\u002F Better: controlled component — value lives in React state\nfunction GoodForm() {\n  const [value, setValue] = useState('')\n  const submit = () => console.log(value) \u002F\u002F just reads state\n  return (\n    \u003C>\n      \u003Cinput value={value} onChange={e => setValue(e.target.value)} \u002F>\n      \u003Cbutton onClick={submit}>Send\u003C\u002Fbutton>\n    \u003C\u002F>\n  )\n}\n```\n\nOther ref-as-smell patterns: using a ref to trigger a re-render manually (use\nstate), using a ref to share data between sibling components (lift state or use\ncontext), and using refs to replicate what `useEffect` dependencies should handle.\n\n**Rule of thumb:** If you're reaching for a ref to solve a problem involving\nrendering or data flow, step back — there's almost certainly a declarative solution\nusing state, context, or derived values.\n",{"id":90,"difficulty":50,"q":91,"a":92},"ref-render-cycle","Why can't you reliably read a ref's value during the render phase?","Refs are **mutable and not part of React's render model**. React may render a\ncomponent multiple times before committing (strict mode renders twice in dev;\nconcurrent mode may discard renders). Refs are only reliably up to date **after\ncommit** (i.e. inside effects and event handlers).\n\n```jsx\nfunction Broken() {\n  const ref = useRef(0)\n  ref.current++ \u002F\u002F mutated during render — unreliable in concurrent mode\n\n  \u002F\u002F If React renders this component but discards the output,\n  \u002F\u002F ref.current has been incremented but nothing was committed.\n  return \u003Cp>Renders: {ref.current}\u003C\u002Fp> \u002F\u002F stale \u002F unpredictable\n}\n\n\u002F\u002F Correct: mutate refs only in effects or event handlers\nfunction Fixed() {\n  const ref = useRef(0)\n  useEffect(() => { ref.current++ }) \u002F\u002F runs only after commit\n  return \u003Cp>...\u003C\u002Fp>\n}\n```\n\nReading a ref in JSX (displaying `ref.current`) is also fragile because React\nwon't schedule a re-render when `.current` changes, so the displayed value goes\nstale silently.\n\n**Rule of thumb:** Treat refs as post-commit bookkeeping — read and write `.current`\nonly inside effects and event handlers, never directly in the render function body.\n",{"id":94,"difficulty":25,"q":95,"a":96},"portal-vs-conditional-render","When should you use a portal versus simply conditionally rendering a component at the top of the tree?","Both approaches can place a modal high in the DOM, but they have different\ntradeoffs for **component co-location** and **CSS context**.\n\n```jsx\n\u002F\u002F Option A: lift modal to App — modal logic scattered from its trigger\nfunction App() {\n  const [open, setOpen] = useState(false)\n  return (\n    \u003C>\n      {open && \u003CModal onClose={() => setOpen(false)} \u002F>} {\u002F* far from trigger *\u002F}\n      \u003CDeepTree onTrigger={() => setOpen(true)} \u002F>\n    \u003C\u002F>\n  )\n}\n\n\u002F\u002F Option B: portal — modal stays co-located with its trigger, escapes DOM\nfunction TriggerButton() {\n  const [open, setOpen] = useState(false)\n  return (\n    \u003C>\n      \u003Cbutton onClick={() => setOpen(true)}>Open Modal\u003C\u002Fbutton>\n      {open && \u003CModal onClose={() => setOpen(false)} \u002F>} {\u002F* portal inside *\u002F}\n    \u003C\u002F>\n  )\n}\n```\n\nPortals let you keep state and logic **co-located** with the component that owns\nthem while still rendering the output at the top of the DOM. Lifting state\nachieves DOM placement but couples the modal logic to a distant ancestor.\n\n**Rule of thumb:** Use a portal when co-location matters and a CSS escape is\nneeded; lift state to the root only when multiple unrelated components need to\ncontrol the same modal.\n",{"id":98,"difficulty":14,"q":99,"a":100},"ref-scroll-measure","How do you use a ref to scroll to an element or measure its dimensions?","Attaching a ref to a DOM element gives you the raw `HTMLElement`, so you can\ncall any imperative DOM API — `scrollIntoView`, `getBoundingClientRect`,\n`offsetHeight`, etc. Measurements must happen **after the element has rendered**,\nwhich means inside a `useEffect` or an event handler.\n\n```jsx\nfunction ChatWindow({ messages }) {\n  const bottomRef = useRef(null)\n  const containerRef = useRef(null)\n  const [containerHeight, setContainerHeight] = useState(0)\n\n  \u002F\u002F Scroll to bottom whenever messages change\n  useEffect(() => {\n    bottomRef.current?.scrollIntoView({ behavior: 'smooth' })\n  }, [messages])\n\n  \u002F\u002F Measure container after first render\n  useEffect(() => {\n    if (containerRef.current) {\n      setContainerHeight(containerRef.current.getBoundingClientRect().height)\n    }\n  }, [])\n\n  return (\n    \u003Cdiv ref={containerRef} className=\"chat-window\">\n      {messages.map(m => \u003CMessage key={m.id} data={m} \u002F>)}\n      \u003Cdiv ref={bottomRef} \u002F> {\u002F* sentinel element at the bottom *\u002F}\n    \u003C\u002Fdiv>\n  )\n}\n```\n\nFor **layout measurements that need to happen before the browser paints**, use\n`useLayoutEffect` instead of `useEffect` — it fires synchronously after DOM\nmutations but before the browser renders, avoiding a visible flash.\n\n**Rule of thumb:** Use `useEffect` for scroll commands (visual, after paint is\nfine) and `useLayoutEffect` for measurements that affect what gets rendered,\nsince those must happen before the user sees the result.\n",{"id":102,"difficulty":14,"q":103,"a":104},"multiple-portals","Can you render multiple portals on the same page, and how should you manage their mount points?","Yes — you can have as many portals as you like. Each `createPortal` call is\nindependent and can target the **same or different** DOM nodes.\n\n```jsx\n\u002F\u002F index.html — dedicated mount points alongside #root\n\u002F\u002F \u003Cdiv id=\"root\">\u003C\u002Fdiv>\n\u002F\u002F \u003Cdiv id=\"modal-root\">\u003C\u002Fdiv>\n\u002F\u002F \u003Cdiv id=\"tooltip-root\">\u003C\u002Fdiv>\n\nfunction Tooltip({ content, anchor }) {\n  return createPortal(\n    \u003Cdiv className=\"tooltip\">{content}\u003C\u002Fdiv>,\n    document.getElementById('tooltip-root') \u002F\u002F dedicated node\n  )\n}\n\nfunction Modal({ children }) {\n  return createPortal(\n    \u003Cdiv className=\"modal-overlay\">{children}\u003C\u002Fdiv>,\n    document.getElementById('modal-root') \u002F\u002F separate node for stacking control\n  )\n}\n```\n\nSeparating mount nodes gives you **CSS stacking control** between portal types —\n`#modal-root` can have a higher `z-index` base than `#tooltip-root` at the body\nlevel, without needing per-element `z-index` wars.\n\n**Rule of thumb:** Provision a dedicated `\u003Cdiv>` for each class of portal\n(modals, tooltips, toasts) in `index.html` so their stacking order is\ndetermined structurally, not by arbitrary `z-index` values.\n",20,null,{"description":11},"React portals and refs interview questions — createPortal, DOM escape hatches, useRef, callback refs, ref forwarding, and direct DOM manipulation patterns.","react\u002Fpatterns\u002Fportals-refs","Portals & Refs","Patterns","patterns","2026-06-24","NfTRyqGUI9gF4wkwTyXVqCdmHs08NsxKVml1iJSZVjc",[116,120,123,127,128],{"subtopic":117,"path":118,"order":119},"Compound Components","\u002Freact\u002Fpatterns\u002Fcompound-components",1,{"subtopic":121,"path":122,"order":12},"Render Props & HOCs","\u002Freact\u002Fpatterns\u002Frender-props-hoc",{"subtopic":124,"path":125,"order":126},"Error Boundaries","\u002Freact\u002Fpatterns\u002Ferror-boundaries",3,{"subtopic":110,"path":21,"order":20},{"subtopic":129,"path":130,"order":131},"forwardRef & useImperativeHandle","\u002Freact\u002Fpatterns\u002Fforward-ref-imperative",5,{"path":133,"title":134},"\u002Fblog\u002Freact-portals-refs-guide","React Portals & Refs — Complete Interview Guide",1782244101297]