Home·Blog·Web Dev
Web Dev

React Performance: Stop Re-renders Before They Kill Your App.

useMemo, useCallback, React.memo, and code splitting — when to use each, when to skip them, and how to measure whether they actually help.

9 min readFeb 2025Ababil.sec

React re-renders are not inherently bad — the framework is designed to handle them efficiently. But unnecessary re-renders of expensive components or large lists can degrade performance significantly. The key is knowing when optimization is needed and choosing the right tool.

Measure First, Optimize Second

Before reaching for useMemo or React.memo, profile your application with React DevTools Profiler. Identify which components are actually slow and how often they re-render. Optimizing components that render in 0.1ms wastes development time and adds code complexity with zero user benefit.

React.memo — For Expensive Component Trees

React.memo prevents a component from re-rendering if its props have not changed (shallow equality). Use it for components that are expensive to render and receive stable props:

const ExpensiveList = React.memo(function ExpensiveList({ items }) {
  return items.map(item => <Item key={item.id} data={item} />);
});

Do not wrap every component with React.memo — the overhead of the equality check outweighs the benefit for cheap components.

useCallback — Stable Function References

Functions defined in render bodies are recreated on every render, causing React.memo children to always re-render. useCallback memoizes the function reference:

const handleClick = useCallback(() => {
  doSomething(id);
}, [id]); // Only changes when id changes

useMemo — Expensive Computations

useMemo memoizes the result of an expensive calculation:

const sortedItems = useMemo(() => {
  return [...items].sort((a, b) => a.price - b.price);
}, [items]);

Only use useMemo when the computation is genuinely expensive (sorting/filtering large arrays, complex math). Do not use it for simple property access or minor transformations.

Code Splitting with Dynamic Imports

Large JavaScript bundles increase Time to Interactive. Split your bundle by importing heavy components lazily:

import dynamic from 'next/dynamic';

const HeavyChart = dynamic(() => import('./HeavyChart'), {
  loading: () => <ChartSkeleton />,
  ssr: false,
});

Virtualization for Long Lists

Rendering 1,000 list items in the DOM is always slow. Use virtualization to render only the visible items. React Virtual and TanStack Virtual are excellent options for Next.js applications.

State Colocation

State placed higher in the tree than necessary causes more components to re-render when it changes. Move state as close as possible to the components that use it. This is often more impactful than any memoization strategy.

Conclusion

React performance optimization is about measurement, not guesswork. Profile first, identify actual bottlenecks, then apply the appropriate tool. State colocation and code splitting often yield bigger wins than memoization. When you do memoize, confirm with the Profiler that it actually reduced renders.

Ready to Secure Your
Project?

Get a professional security audit or start a project with us today.

Start a Project
Related Articles