How to Access a DOM Element in React

Intermediate8 min interview
Skills tested:
Creating a ref with useRef and attaching it to a DOM element via the ref propAccessing ref.current inside useEffect (not during render) and understanding whyUsing callback refs to measure element dimensions on mountUsing React.forwardRef to pass a ref through a component boundary to its inner DOM nodeKnowing what ref.current contains before mount (null) and what operations are safe

Advertisement

🧩 Scenario

DOM access via refs is a common React question. Interviewers test whether you know the ref lifecycle (null during render, available after mount), the difference between refs and state, and advanced patterns like forwardRef.

Architecture Walkthrough

The Ref Lifecycle

useRef() returns a plain JavaScript object { current: null }. React uses the ref prop to populate current after the component mounts: when React finishes rendering and inserting the DOM nodes, it sets ref.current to the actual HTMLElement. When the component unmounts, React sets ref.current back to null.

This timing has a critical implication: ref.current is always null during the render phase. Accessing it during rendering, in a computed value, or in the render return is always reading a null. The correct place to access a DOM ref is inside useEffect, which runs after the component has mounted and the DOM nodes exist. Any DOM operation (focus, scroll, measure, call an imperative API) belongs in useEffect with the ref.

Refs Do Not Trigger Re-renders

Changing ref.current directly is a mutation of a plain object, not a state update. React has no awareness of this change and does not schedule a re-render. This is what makes refs appropriate for values that need to persist across renders without causing renders: a timer ID, a previous value, a pending animation frame. If a value change needs to update the UI, it must go in useState, not useRef.

forwardRef for Component Boundaries

A ref prop passed to a custom function component does not automatically reach the inner DOM node. React does not forward refs by default because it would break component encapsulation. To expose an inner DOM node to a parent, wrap the component in React.forwardRef, which receives both props and ref as arguments and attaches the ref to the desired inner element.


Key Code Explained

import { useRef, useEffect } from 'react';

// Basic pattern: focus input on mount
function AutoFocusInput() {
  const inputRef = useRef<HTMLInputElement>(null);

  useEffect(() => {
    // ref.current is the actual <input> DOM node here
    inputRef.current?.focus();
  }, []); // empty array = runs once after mount

  return <input ref={inputRef} placeholder='I focus on mount' />;
}

// Callback ref: runs when React attaches or detaches the element
// Useful for measuring elements where dimensions are unknown until render
function MeasuredBox() {
  const [height, setHeight] = useState<number | null>(null);

  const measuredRef = useCallback((node: HTMLDivElement | null) => {
    if (node !== null) {
      // node is the DOM element — safe to measure here
      setHeight(node.getBoundingClientRect().height);
    }
  }, []); // stable callback — no recreations on re-render

  return (
    <div>
      <div ref={measuredRef} className='content'>
        Dynamic content with unknown height
      </div>
      {height !== null && <p>Height: {height}px</p>}
    </div>
  );
}

// forwardRef: expose child DOM node to parent
const TextInput = React.forwardRef<HTMLInputElement, { label: string }>(
  ({ label }, ref) => {
    return (
      <label>
        {label}
        {/* ref is forwarded to the actual <input> element */}
        <input ref={ref} type='text' />
      </label>
    );
  },
);

// Parent can now attach a ref to TextInput and access the inner <input>
function Form() {
  const nameRef = useRef<HTMLInputElement>(null);

  const focusName = () => nameRef.current?.focus();

  return (
    <form>
      <TextInput ref={nameRef} label='Full name' />
      <button type='button' onClick={focusName}>
        Focus name field
      </button>
    </form>
  );
}

// Ref vs state: choosing the right tool
function Timer() {
  const [seconds, setSeconds] = useState(0);

  // timerRef stores the interval ID without triggering re-renders
  // If we used useState for the ID, clearing and restarting the timer
  // would cause an extra render, and the ID might be stale in a closure
  const timerRef = useRef<ReturnType<typeof setInterval> | null>(null);

  const start = () => {
    timerRef.current = setInterval(() => {
      setSeconds((prev) => prev + 1); // useState for the visible value
    }, 1000);
  };

  const stop = () => {
    if (timerRef.current !== null) {
      clearInterval(timerRef.current);
    }
  };

  return (
    <div>
      <p>{seconds}s</p>
      <button onClick={start}>Start</button>
      <button onClick={stop}>Stop</button>
    </div>
  );
}

The callback ref pattern (useCallback((node) => ...)) runs synchronously when React attaches or detaches the node. It is the correct approach when you need to measure element dimensions that are only available after the element is in the DOM. A useRef + useEffect pattern can miss measurement if the element's size changes due to content, but a callback ref fires precisely when the node is attached.


Tradeoffs

PatternWhen to useTriggers re-render
useRefStable DOM access, timer IDs, previous valuesNo
useStateValues that affect rendered outputYes
Callback refMeasuring elements on attachmentYes (only when calling setState inside)
forwardRefExposing child DOM node to parentNo

What Interviewers Actually Check

  • Whether you know ref.current is null during render and why
  • Whether you access DOM refs inside useEffect, not directly in the component body
  • Whether you know ref changes do not trigger re-renders
  • Whether you know React.forwardRef is required to pass a ref to a custom component
  • Whether you can distinguish when to use useRef vs useState

Follow-Up Questions

  1. What is useImperativeHandle and when would you use it with forwardRef?
  2. How do you attach a ref to a React component instance (class component) versus a DOM node?
  3. Why might accessing ref.current in an event handler be safe even though it is unsafe during render?
  4. How do you use a ref to track the previous value of a prop across renders?
  5. What happens to a ref when the component re-renders? Does current get replaced with a new DOM node?

Common Candidate Mistakes

  • Reading ref.current during the render function body or in JSX — it is null at this point
  • Using document.querySelector('#my-input') instead of a ref — selects globally, not scoped, and breaks with multiple instances
  • Using a ref to store a value that the UI needs to display — ref changes are invisible to React and the UI will not update
  • Passing ref as a named prop to a custom component without forwardRef — the ref is silently dropped
  • Calling ref.current.focus() without optional chaining — throws if the element has unmounted between effect setup and execution

Interview Readiness Checklist

Before you leave this question, make sure you can answer:

  • Can you attach a ref to a DOM element and access it in useEffect?
  • Can you explain why ref.current is null during render?
  • Can you write a callback ref to measure an element's height after mount?
  • Can you use React.forwardRef to expose a child component's DOM node to a parent?
  • Can you explain when to use useRef versus useState for mutable values?

Summary

useRef creates a { current: null } object that React populates with the DOM node after mount. The ref prop on a JSX element binds the ref to that node. Access ref.current only inside useEffect (or event handlers), never during rendering, because the DOM node does not exist yet during the render phase. Changing ref.current does not trigger a re-render. Callback refs (a function passed as the ref prop) fire synchronously when the node is attached, making them ideal for measuring elements whose dimensions are unknown until render. To pass a ref through a component boundary, wrap the component in React.forwardRef, which receives both props and ref as separate arguments and attaches the ref to the intended inner element.

Related Videos
Watch these videos to learn more about this topic
Frequently Asked Questions

Can I use document.querySelector to access DOM elements in React?

Technically yes, but it is wrong. document.querySelector has no awareness of which component instance you want, it searches the entire document, and it can select elements from other components. Refs scope DOM access to the specific element instance and are the correct React pattern.

Advertisement


Stay Updated

Get the latest frontend challenges, interview questions and tutorials delivered to your inbox.

Advertisement