RE

useRef

React · 6 entries

DOM Refs

syntax
const ref = useRef(null);
// Attach to element
<element ref={ref} />
// Access
ref.current
example
import { useRef } from 'react';

function AutoFocusInput() {
  const inputRef = useRef(null);

  function handleClick() {
    inputRef.current.focus();
    inputRef.current.select();
  }

  return (
    <div>
      <input ref={inputRef} defaultValue="Edit me" />
      <button onClick={handleClick}>Focus & Select</button>
    </div>
  );
}

Note ref.current is null during the first render and gets assigned after the DOM node mounts. Never read or write ref.current during rendering -- only inside effects or event handlers.

Mutable Values (No Re-render)

syntax
const valueRef = useRef(initialValue);
valueRef.current = newValue; // does NOT trigger re-render
example
function ClickTracker() {
  const clickCount = useRef(0);

  function handleClick() {
    clickCount.current += 1;
    console.log(`Clicked ${clickCount.current} times`);
  }

  return <button onClick={handleClick}>Click me</button>;
}

// Common use: storing previous value
function usePrevious(value) {
  const prevRef = useRef(undefined);
  const previous = prevRef.current;
  prevRef.current = value;
  return previous;
}

Note useRef is like an instance variable for function components. Updating .current does not cause a re-render. Use it for values you need to persist across renders without triggering UI updates: timer IDs, previous values, flags.

Forwarding Refs (React 19)

syntax
// React 19: ref is a regular prop
function CustomInput({ ref, ...props }) {
  return <input ref={ref} {...props} />;
}

// Pre-React 19 (still works):
const CustomInput = forwardRef((props, ref) => {
  return <input ref={ref} {...props} />;
});
example
// React 19 style: ref is just a prop
function FancyInput({ ref, placeholder, className }) {
  return (
    <input
      ref={ref}
      placeholder={placeholder}
      className={`fancy-input ${className}`}
    />
  );
}

// Parent component
function SearchBar() {
  const inputRef = useRef(null);

  return (
    <div>
      <FancyInput ref={inputRef} placeholder="Search..." />
      <button onClick={() => inputRef.current.focus()}>Focus</button>
    </div>
  );
}

Note In React 19, ref is passed as a regular prop to function components. forwardRef is no longer necessary and is considered deprecated. Existing code using forwardRef still works but should be migrated over time.

Callback Refs

syntax
<element ref={(node) => {
  // node is the DOM element or null on unmount
}} />
example
function MeasuredBox() {
  const [height, setHeight] = useState(0);

  const measureRef = (node) => {
    if (node !== null) {
      setHeight(node.getBoundingClientRect().height);
    }
  };

  return (
    <div>
      <div ref={measureRef} style={{ padding: '20px' }}>
        <p>This box has dynamic content.</p>
        <p>Its height is measured via a callback ref.</p>
      </div>
      <p>Measured height: {height}px</p>
    </div>
  );
}

Note Callback refs fire whenever the ref attaches or detaches. In React 19, callback refs can return a cleanup function that runs when the element is removed, similar to useEffect cleanup. Useful for measuring elements or integrating third-party DOM libraries.

useImperativeHandle

syntax
useImperativeHandle(ref, () => ({
  customMethod() { ... },
}), [deps]);
example
import { useRef, useImperativeHandle } from 'react';

function VideoPlayer({ ref, src }) {
  const videoRef = useRef(null);

  useImperativeHandle(ref, () => ({
    play() {
      videoRef.current.play();
    },
    pause() {
      videoRef.current.pause();
    },
    restart() {
      videoRef.current.currentTime = 0;
      videoRef.current.play();
    },
  }));

  return <video ref={videoRef} src={src} />;
}

// Parent controls the player
function MediaPage() {
  const playerRef = useRef(null);

  return (
    <div>
      <VideoPlayer ref={playerRef} src="/video.mp4" />
      <button onClick={() => playerRef.current.play()}>Play</button>
      <button onClick={() => playerRef.current.restart()}>Restart</button>
    </div>
  );
}

Note useImperativeHandle restricts what the parent can access through the ref. Expose only the methods the parent needs instead of the entire DOM node. Combine with ref-as-prop in React 19 (no forwardRef needed).

Storing Timer IDs

syntax
const timerRef = useRef(null);
// Start
timerRef.current = setTimeout(fn, ms);
// Clear
clearTimeout(timerRef.current);
example
function DebouncedSearch({ onSearch }) {
  const [query, setQuery] = useState('');
  const timerRef = useRef(null);

  function handleChange(e) {
    const value = e.target.value;
    setQuery(value);

    clearTimeout(timerRef.current);
    timerRef.current = setTimeout(() => {
      onSearch(value);
    }, 400);
  }

  useEffect(() => {
    return () => clearTimeout(timerRef.current);
  }, []);

  return <input value={query} onChange={handleChange} />;
}

Note Store timer IDs in a ref so they survive re-renders without causing them. Always clear the timer on unmount via useEffect cleanup to prevent firing after the component is gone.