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
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.