useRef, useMemo & useCallback
These three hooks let you optimize performance and access DOM elements directly. useRef persists values without triggering re-renders. useMemo caches expensive computations. useCallback caches function references.
1 useRef
React — useRef
import { useRef, useEffect } from "react";
function VideoPlayer() {
const videoRef = useRef(null); // DOM reference
useEffect(() => {
videoRef.current.play(); // direct DOM access
}, []);
return <video ref={videoRef} src="/intro.mp4" />;
}
// useRef for mutable values (won't cause re-render)
function Timer() {
const [running, setRunning] = useState(false);
const intervalRef = useRef(null);
const start = () => {
intervalRef.current = setInterval(() => tick(), 1000);
setRunning(true);
};
const stop = () => {
clearInterval(intervalRef.current);
setRunning(false);
};
return <button onClick={running ? stop : start}>{running ? "Stop" : "Start"}</button>;
}
2 useMemo & useCallback
React — useMemo & useCallback
import { useMemo, useCallback } from "react";
function DataTable({ data, filter }) {
// useMemo — only recomputes when data or filter changes
const filtered = useMemo(() => {
console.log("Filtering..."); // expensive computation
return data.filter(row => row.name.includes(filter));
}, [data, filter]);
// useCallback — stable function reference for child components
const handleDelete = useCallback((id) => {
setData(prev => prev.filter(row => row.id !== id));
}, []); // no deps — function never needs to change
return (
<ul>
{filtered.map(row => (
<Row key={row.id} data={row} onDelete={handleDelete} />
))}
</ul>
);
}
// Wrap child with React.memo to prevent unnecessary re-renders
const Row = React.memo(({ data, onDelete }) => (
<li>{data.name} <button onClick={() => onDelete(data.id)}>Delete</button></li>
));
3 Code Challenge
Challenge: Build a sortable table of 1000 rows. Use
useMemo to sort the data only when the sort column changes. Wrap individual row components in React.memo and verify fewer re-renders using React DevTools.