Skip to main content
useRef returns a mutable ref object whose .current property is initialized to the passed argument. The returned object persists for the full lifetime of the component.

Signature

function useRef<T>(initialValue: T): RefObject<T>
function useRef<T>(initialValue: T | null): RefObject<T | null>
function useRef<T>(initialValue: T | undefined): RefObject<T | undefined>

Parameters

initialValue
T
required
The initial value to store in the ref object’s .current property. Can be of any type. After initialization, you can mutate the .current property freely.

Returns

Returns a mutable ref object with a single property:
  • current: Initially set to initialValue. You can read and write to this property. Changes to it do not cause re-renders.

Accessing DOM Elements

import { useRef } from 'preact/hooks';

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

  const focusInput = () => {
    // Access DOM element and call its method
    inputRef.current.focus();
  };

  return (
    <div>
      <input ref={inputRef} type="text" />
      <button onClick={focusInput}>Focus Input</button>
    </div>
  );
}

Storing Mutable Values

function Timer() {
  const [count, setCount] = useState(0);
  const intervalRef = useRef(null);

  const start = () => {
    if (intervalRef.current !== null) return;

    intervalRef.current = setInterval(() => {
      setCount(c => c + 1);
    }, 1000);
  };

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

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={start}>Start</button>
      <button onClick={stop}>Stop</button>
    </div>
  );
}

Keeping Previous Values

function Counter({ value }) {
  const previousValue = useRef(value);

  useEffect(() => {
    previousValue.current = value;
  }, [value]);

  return (
    <div>
      <p>Current: {value}</p>
      <p>Previous: {previousValue.current}</p>
    </div>
  );
}

Avoiding Stale Closures

function ChatRoom({ roomId }) {
  const [message, setMessage] = useState('');
  const callbackRef = useRef(null);

  // Store latest callback in ref
  callbackRef.current = () => {
    console.log(`Sending "${message}" to room ${roomId}`);
  };

  useEffect(() => {
    const interval = setInterval(() => {
      // Always calls the latest callback
      callbackRef.current?.();
    }, 5000);

    return () => clearInterval(interval);
  }, [roomId]); // Only re-subscribe when roomId changes

  return (
    <input
      value={message}
      onChange={e => setMessage(e.target.value)}
    />
  );
}

Measuring DOM Elements

function MeasureBox() {
  const boxRef = useRef(null);
  const [dimensions, setDimensions] = useState({ width: 0, height: 0 });

  useLayoutEffect(() => {
    if (boxRef.current) {
      const { width, height } = boxRef.current.getBoundingClientRect();
      setDimensions({ width, height });
    }
  }, []);

  return (
    <div>
      <div ref={boxRef} style={{ width: '50%', height: '200px' }}>
        Content
      </div>
      <p>Width: {dimensions.width}px</p>
      <p>Height: {dimensions.height}px</p>
    </div>
  );
}

Implementing Instance Variables

function VideoPlayer({ src }) {
  const videoRef = useRef(null);
  const isPlayingRef = useRef(false);
  const [, forceUpdate] = useState();

  const togglePlay = () => {
    if (isPlayingRef.current) {
      videoRef.current.pause();
      isPlayingRef.current = false;
    } else {
      videoRef.current.play();
      isPlayingRef.current = true;
    }
    forceUpdate({}); // Force re-render to update button text
  };

  return (
    <div>
      <video ref={videoRef} src={src} />
      <button onClick={togglePlay}>
        {isPlayingRef.current ? 'Pause' : 'Play'}
      </button>
    </div>
  );
}

Tracking Render Count

function RenderCounter() {
  const renderCount = useRef(0);

  // Increment on every render without causing re-render
  renderCount.current++;

  return <div>This component has rendered {renderCount.current} times</div>;
}

Forwarding Refs

import { forwardRef, useRef } from 'preact/compat';

const FancyInput = forwardRef((props, ref) => {
  return <input ref={ref} className="fancy-input" {...props} />;
});

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

  const focusInput = () => {
    inputRef.current.focus();
  };

  return (
    <div>
      <FancyInput ref={inputRef} />
      <button onClick={focusInput}>Focus</button>
    </div>
  );
}

Debouncing with Ref

function SearchInput() {
  const [query, setQuery] = useState('');
  const timeoutRef = useRef(null);

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

    // Clear previous timeout
    if (timeoutRef.current) {
      clearTimeout(timeoutRef.current);
    }

    // Set new timeout
    timeoutRef.current = setTimeout(() => {
      console.log('Searching for:', value);
      // Perform search
    }, 500);
  };

  return <input value={query} onChange={handleChange} />;
}
useRef is useful for more than just the ref attribute. It’s handy for keeping any mutable value around, similar to how you’d use instance fields in classes.
Mutating the .current property does not cause a re-render. If you need to trigger a re-render when a value changes, use useState instead.
The ref object returned by useRef is the same object on every render. It’s a plain JavaScript object that you can mutate freely.

Build docs developers (and LLMs) love