Skip to main content
Refs provide a way to access DOM nodes or Preact elements created in the render method. They’re also useful for storing mutable values that persist across renders without causing re-renders.

When to Use Refs

Refs are useful for:
  • Managing focus, text selection, or media playback
  • Triggering imperative animations
  • Integrating with third-party DOM libraries
  • Storing values that don’t affect rendering
Don’t overuse refs. Most things should be done declaratively with props and state.

Creating Refs

Preact provides two ways to create refs:

Accessing DOM Elements

The most common use of refs is to access DOM elements directly:
import { useRef, useEffect } from 'preact/hooks';

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

  useEffect(() => {
    // Focus input on mount
    inputRef.current.focus();
  }, []);

  return <input ref={inputRef} placeholder="Search..." />;
}

Callback Refs

Instead of passing a ref object, you can pass a function. Preact will call it with the DOM element:
import { useState } from 'preact/hooks';

function MeasureElement() {
  const [height, setHeight] = useState(0);

  const measuredRef = element => {
    if (element) {
      setHeight(element.getBoundingClientRect().height);
    }
  };

  return (
    <div>
      <div ref={measuredRef}>
        <p>This element's height is: {height}px</p>
      </div>
    </div>
  );
}
Callback refs are called with null when the component unmounts. Always check if the element exists.

Storing Mutable Values

Refs aren’t just for DOM elements. They’re perfect for storing values that need to persist across renders but shouldn’t trigger re-renders when changed:
import { useRef, useState, useEffect } from 'preact/hooks';

function Stopwatch() {
  const [time, setTime] = useState(0);
  const intervalRef = useRef(null);

  const start = () => {
    if (intervalRef.current) return; // Already running
    
    intervalRef.current = setInterval(() => {
      setTime(t => t + 1);
    }, 1000);
  };

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

  const reset = () => {
    stop();
    setTime(0);
  };

  useEffect(() => {
    return () => stop(); // Cleanup on unmount
  }, []);

  return (
    <div>
      <div>Time: {time}s</div>
      <button onClick={start}>Start</button>
      <button onClick={stop}>Stop</button>
      <button onClick={reset}>Reset</button>
    </div>
  );
}

Forwarding Refs

Sometimes you need to pass a ref through a component to one of its children. Use forwardRef from preact/compat:
1
Basic Ref Forwarding
2
import { forwardRef } from 'preact/compat';

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

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

  return (
    <div>
      <FancyInput ref={inputRef} />
      <button onClick={() => inputRef.current.focus()}>
        Focus Input
      </button>
    </div>
  );
}
3
With useImperativeHandle
4
Customize what the ref exposes using useImperativeHandle:
5
import { forwardRef } from 'preact/compat';
import { useRef, useImperativeHandle } from 'preact/hooks';

const VideoPlayer = forwardRef((props, ref) => {
  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={props.src} />;
});

// Usage
function Parent() {
  const playerRef = useRef(null);

  return (
    <div>
      <VideoPlayer ref={playerRef} src="video.mp4" />
      <button onClick={() => playerRef.current.play()}>Play</button>
      <button onClick={() => playerRef.current.pause()}>Pause</button>
      <button onClick={() => playerRef.current.restart()}>Restart</button>
    </div>
  );
}
6
Implementation Details
7
The forwardRef function is implemented in compat/src/forwardRef.js:
8
// From compat/src/forwardRef.js:12-31
export function forwardRef(fn) {
  function Forwarded(props) {
    let clone = assign({}, props);
    delete clone.ref;
    return fn(clone, props.ref || null);
  }

  Forwarded.$$typeof = Symbol.for('react.forward_ref');
  Forwarded.render = fn;
  Forwarded.prototype.isReactComponent = true;
  Forwarded.displayName = 'ForwardRef(' + (fn.displayName || fn.name) + ')';

  return Forwarded;
}
9
Reference: compat/src/forwardRef.js:12-31

Ref Best Practices

1
Check for null
2
Always check if the ref exists before accessing it:
3
function Component() {
  const ref = useRef(null);

  const handleClick = () => {
    // Good: Check before accessing
    if (ref.current) {
      ref.current.focus();
    }
  };

  return <input ref={ref} />;
}
4
Don’t access refs during render
5
Refs should be accessed in event handlers or effects, not during render:
6
function Component() {
  const ref = useRef(null);

  // Bad: Accessing during render
  const width = ref.current?.offsetWidth;

  // Good: Access in effect
  useEffect(() => {
    const width = ref.current?.offsetWidth;
    console.log(width);
  }, []);

  return <div ref={ref}>Content</div>;
}
7
Use refs sparingly
8
Most things should be done declaratively. Only use refs when necessary:
9
// Bad: Using ref for something that should be state
function Component() {
  const countRef = useRef(0);
  
  return (
    <button onClick={() => countRef.current++}>
      Count: {countRef.current} {/* Won't update! */}
    </button>
  );
}

// Good: Use state for values that affect rendering
function Component() {
  const [count, setCount] = useState(0);
  
  return (
    <button onClick={() => setCount(c => c + 1)}>
      Count: {count}
    </button>
  );
}
10
Clean up in effects
11
When using refs with effects, clean up properly:
12
function Component() {
  const ref = useRef(null);

  useEffect(() => {
    const element = ref.current;
    if (!element) return;

    const observer = new ResizeObserver(() => {
      console.log('Resized');
    });

    observer.observe(element);

    return () => {
      observer.unobserve(element);
    };
  }, []);

  return <div ref={ref}>Content</div>;
}

Common Patterns

import { useRef, useEffect } from 'preact/hooks';

function AutoFocusInput({ autoFocus, ...props }) {
  const ref = useRef(null);

  useEffect(() => {
    if (autoFocus && ref.current) {
      ref.current.focus();
    }
  }, [autoFocus]);

  return <input ref={ref} {...props} />;
}

Next Steps

Hooks

Learn about useRef and other hooks in depth

Fragments

Return multiple elements without wrapper nodes

Build docs developers (and LLMs) love