useRef and useImperativeHandle
useRef - DOM Access and Mutable Values
useRef creates a mutable reference that persists across renders without causing re-renders when changed.
DOM References
function TextInput() {
const inputRef = useRef<HTMLInputElement>(null);
const focusInput = () => {
inputRef.current?.focus();
};
return (
<>
<input ref={inputRef} type='text' />
<button onClick={focusInput}>Focus Input</button>
</>
);
}
Storing Mutable Values
function Timer() {
const [count, setCount] = useState(0);
const intervalRef = useRef<number | null>(null);
const start = () => {
if (intervalRef.current !== null) return;
intervalRef.current = setInterval(() => {
setCount(c => c + 1);
}, 1000);
};
const stop = () => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
};
useEffect(() => {
return () => stop(); // Cleanup
}, []);
return (
<div>
<p>Count: {count}</p>
<button onClick={start}>Start</button>
<button onClick={stop}>Stop</button>
</div>
);
}
Previous Value Tracking
function usePrevious\<T\>(value: T): T | undefined {
const ref = useRef\<T\>();
useEffect(() => {
ref.current = value;
}, [value]);
return ref.current;
}
// Usage
function Counter() {
const [count, setCount] = useState(0);
const prevCount = usePrevious(count);
return (
<div>
<p>Current: {count}</p>
<p>Previous: {prevCount}</p>
<button onClick={() => setCount(count + 1)}>+</button>
</div>
);
}
useImperativeHandle - Custom Ref API
import { forwardRef, useImperativeHandle, useRef } from 'react';
interface VideoPlayerHandle {
play: () => void;
pause: () => void;
reset: () => void;
}
const VideoPlayer = forwardRef<VideoPlayerHandle>((props, ref) => {
const videoRef = useRef<HTMLVideoElement>(null);
useImperativeHandle(ref, () => ({
play() {
videoRef.current?.play();
},
pause() {
videoRef.current?.pause();
},
reset() {
if (videoRef.current) {
videoRef.current.currentTime = 0;
videoRef.current.pause();
}
},
}));
return <video ref={videoRef} src='video.mp4' />;
});
// Usage
function App() {
const playerRef = useRef<VideoPlayerHandle>(null);
return (
<>
<VideoPlayer ref={playerRef} />
<button onClick={() => playerRef.current?.play()}>Play</button>
<button onClick={() => playerRef.current?.pause()}>Pause</button>
<button onClick={() => playerRef.current?.reset()}>Reset</button>
</>
);
}
Next Steps
- Learn about useMemo and useCallback