Home About Skills Products Work
Projects Services Experience
Learn
Tutorials Courses Blogs Resources
Contact
Blog · JavaScript

React Performance Optimization: Memoization and Code Splitting

React Performance Optimization: Memoization and Code Splitting

React re-renders a component whenever its state or props change — usually fine, but in a large app with expensive computations or deep component trees, unnecessary re-renders become a real, measurable performance problem. Here's how to actually fix it, not just guess at it.

Measure First

Before optimizing anything, open React DevTools' Profiler tab and record an interaction. It shows exactly which components re-rendered and how long each took — optimizing a component that re-renders in 0.1ms is wasted effort; the Profiler tells you where the real cost actually is.

React.memo: Skipping Re-renders on Unchanged Props

const ExpensiveRow = React.memo(function ExpensiveRow({ item }) {
  return <div>{item.name}</div>;
});

React.memo skips re-rendering if props are shallow-equal to the last render. It's useless (or actively harmful) if you're passing a new object/array/function literal as a prop on every render — that always fails the shallow-equality check.

useMemo and useCallback: Stabilizing Values

const filteredItems = useMemo(
  () => items.filter(i => i.category === activeCategory),
  [items, activeCategory]
);

const handleClick = useCallback(() => {
  doSomething(activeCategory);
}, [activeCategory]);

useMemo caches an expensive computed VALUE; useCallback caches a stable FUNCTION reference. Both exist for the same reason: prevent a child wrapped in React.memo from re-rendering just because its parent handed it a brand-new object/function on every render.

Don't Overuse Memoization

Wrapping every single component in React.memo and every value in useMemo "just in case" adds real overhead (the comparison itself costs something) and makes code harder to read for no measured benefit. Reach for these tools specifically where the Profiler shows an actual problem — a list re-rendering hundreds of rows, an expensive sort/filter recalculating on every keystroke.

Code Splitting with React.lazy

const AdminDashboard = React.lazy(() => import('./AdminDashboard'));

function App() {
  return (
    <Suspense fallback={<Spinner />}>
      <AdminDashboard />
    </Suspense>
  );
}

This splits AdminDashboard (and everything it imports) into a separate JS bundle that only downloads when a user actually navigates there — a huge win for initial page-load time on any app with routes most visitors never touch, like an admin panel on a public-facing site.

Virtualizing Long Lists

Rendering 10,000 DOM nodes for a 10,000-row list is slow regardless of memoization — the fix is rendering only the ~20 rows currently visible in the viewport, using a library like react-window or @tanstack/react-virtual. This is the single biggest win available for any genuinely long list.

JavaScript Fundamentals Every Beginner Should Master

JavaScript Fundamentals Every Beginner Should Master

Variables, functions, arrays, objects, and the DOM — the plain JavaScript every framework is built on top of.

Async/Await and Promises in JavaScript: A Practical Guide

How Promises actually work, why async/await is just readable syntax on top of them, and the parallel-fetching mistake almost everyone makes.

Modern JavaScript (ES6+) Features You Should Be Using

Modern JavaScript (ES6+) Features You Should Be Using

Template literals, destructuring, optional chaining, and modules — the JavaScript you actually see in every modern codebase.

Esc