Most React apps don't need manual optimization — React is fast by default. Reach for these tools only after you've actually noticed a performance problem (use the React DevTools Profiler to confirm it), not preemptively.
React.memo — skip re-rendering when props haven't changed
const StudentCard = React.memo(function StudentCard({ name, cgpa }) {
console.log('rendering', name);
return <div>{name} — {cgpa}</div>;
});
memo does a shallow comparison of props; if none changed, React reuses the previous render output and skips re-rendering that subtree.
useMemo — cache an expensive calculation
function Dashboard({ students }) {
const topStudents = useMemo(
() => students.filter(s => s.cgpa >= 3.5).sort((a, b) => b.cgpa - a.cgpa),
[students] // only recompute when `students` changes
);
return <StudentList students={topStudents} />;
}
useCallback — cache a function reference
Without useCallback, a new function is created on every render, which can defeat React.memo on a child that receives it as a prop:
function ParentComponent() {
const [count, setCount] = useState(0);
// stable reference across renders unless dependencies change
const handleSave = useCallback((id) => {
console.log('saving', id);
}, []);
return <ExpensiveChild onSave={handleSave} />;
}
const ExpensiveChild = React.memo(function ExpensiveChild({ onSave }) {
// only re-renders when onSave (or other props) actually changes
return <button onClick={() => onSave(1)}>Save</button>;
});
Code splitting with lazy and Suspense
Load large components (routes, modals, charts) only when they're actually needed, shrinking the initial bundle:
import { lazy, Suspense } from 'react';
const AnalyticsChart = lazy(() => import('./AnalyticsChart'));
function Dashboard() {
return (
<Suspense fallback={<p>Loading chart...</p>}>
<AnalyticsChart />
</Suspense>
);
}
List virtualization
For lists with hundreds or thousands of rows, render only the visible ones using a library like TanStack Virtual or react-window instead of rendering the entire list at once.
A quick checklist before optimizing
- Profile first — don't guess where the slowness is.
- Move state as close as possible to where it's used, so unrelated components don't re-render.
- Use the
childrenprop pattern to keep expensive subtrees out of a re-rendering parent. - Only then reach for
memo/useMemo/useCallbackon the components that are actually shown to be slow.