Understanding useRef for Efficient DOM Manipulation in React

Understanding useRef for Efficient DOM Manipulation in React

In React, useRef stores a reference to a value that can be mutated without causing re-rendering of the component.

While the value can be mutated, it should not occur during the rendering phase. Instead, mutations should happen in response to user actions or state changes, or inside a useEffect hook or inside some event listener or something else.

It is commonly used for directly manipulating the DOM, by using the reference to an element.

Example:

const StopWatch = () => {
  const [startTime, setStartTime] = useState<number>(0);
  const intervalRef = useRef<number>(0);
  const [endTime, setEndTime] = useState<number>(0);

  const onStartClick = () => {
    setStartTime(Date.now());
    clearInterval(intervalRef.current);
    const interval = setInterval(() => {
      setEndTime(Date.now());
    }, 10);

    intervalRef.current= interval;
  }

  const onStopClick = () => {
    clearInterval(intervalRef.current);
    setStartTime(0);
    setEndTime(0);
    intervalRef.current = null;
  }

  return (
    <div>
      <p>{(endTime-startTime)/1000}</p>
      <button onClick={onStartClick} disabled={endTime > 0}>Start</button>
      <button onClick={onStopClick} disabled={endTime <= 0}>Stop</button>
    </div>
  )

}

In this example, the stopwatch timer is controlled with useRef for handling the interval.

Passing ref to Child Components with forwardRef

In React versions prior to 19, forwardRef was used for passing a ref to a child component from parent. This gave parent access to child DOM and its nodes and perform whatsoever action it want to perform.

Example:

const Form = () => {
  const ref = useRef<HTMLInputElement>(null);

  const handleClick = () => {
    if (ref) {
      ref.current?.focus();
    }
  }

  return (
    <>
      <Input ref={ref} />
      <button onClick={handleClick}>Focus</button>
    </>
  );
}

const Input = forwardRef(function (props, ref) {
  return <input ref={ref} />
});

forwardRef takes two parameter one is props and other is ref.

In our example Form is parent and Input is child. You can see onClicking button in parent the input element is focussed.

In React 19, we can do straight away without forwardRef by passing ref as props of child component

// Parent will be as it is only child will change
const Input = ({ref}) => (<input ref={ref} />);

Controlling What Gets Exposed with useImperativeHandle

When you use ref in React to reference a element, it can expose the entire DOM node.This might not always be desirable, especially when you only want to expose specific methods or functionality. To solve this problem, useImperativeHandle is used when we passed ref from parent to child.

const Input = forwardRef(function (props, ref) {
  const inputRef = useRef<HTMLInputElement>(null);
  useImperativeHandle(ref, () => {
    return {
      focus() {
        inputRef.current?.focus();
      }
    }
  }, [])
  return (<input ref={inputRef} />);
});

const Form = () => {
  const ref = useRef<HTMLInputElement>(null);

  const handleClick = () => {
    if (ref) {
      // This will only have access to focus from child and nothing else
      ref.current?.focus();
    }
  }

  return (
    <>
      <Input ref={ref} />
      <button onClick={handleClick}>Focus</button>
    </>
  );
}

const Input = forwardRef(function (props, ref) {
  return <input ref={ref} />
});

This blog explores the use of useRef in React for efficient DOM manipulation, highlighting how to pass refs to child components (including the new approach in React 19), and control which methods or properties are exposed using useImperativeHandle