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
Creating Refs
Preact provides two ways to create refs:- useRef Hook (Recommended)
- createRef
The It returns an object with a
useRef hook is the recommended way to create refs in function components:How useRef Works
TheuseRef hook is implemented in hooks/src/index.js as a simple wrapper around useMemo:current property that persists across renders.Reference: hooks/src/index.js:306-309Accessing DOM Elements
The most common use of refs is to access DOM elements directly:Callback Refs
Instead of passing a ref object, you can pass a function. Preact will call it with the DOM element: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:Forwarding Refs
Sometimes you need to pass a ref through a component to one of its children. UseforwardRef from preact/compat:
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>
);
}
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>
);
}
// 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;
}
Ref Best Practices
function Component() {
const ref = useRef(null);
const handleClick = () => {
// Good: Check before accessing
if (ref.current) {
ref.current.focus();
}
};
return <input ref={ref} />;
}
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>;
}
// 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>
);
}
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
Next Steps
Hooks
Learn about useRef and other hooks in depth
Fragments
Return multiple elements without wrapper nodes