Technology · React

React Memo and Lazy Loading

Optimize component rendering with React.memo, lazy loading, and code splitting strategies.

TL;DR
  1. 01Use React.memo to skip re-renders when props haven't changed.
  2. 02Use lazy loading to split code and load components on demand.
  3. 03Combine both for significant performance improvements.

React.memo

  • Memoize a component to skip re-renders when props are the same.
    const UserCard = React.memo(({ user }) => (
      <div>{user.name}</div>
    ));
  • React.memo compares new and old props using shallow equality.
  • Only skip re-renders if props haven't changed.
    // Parent re-renders but UserCard doesn't if user prop is the same
    const Parent = () => {
      const [count, setCount] = useState(0);
      const user = { id: 1, name: "Alice" };
      
      return (
        <>
          <UserCard user={user} />
          <button onClick={() => setCount(count + 1)}>
            Count: {count}
          </button>
        </>
      );
    };
  • Use custom comparison for complex props.
    const UserCard = React.memo(
      ({ user }) => <div>{user.name}</div>,
      (prevProps, nextProps) => {
        return prevProps.user.id === nextProps.user.id;
      }
    );
  • Memoization has overhead, so only use when re-renders are expensive.

Lazy Loading

  • Use React.lazy to split code and load components on demand.
    import { lazy, Suspense } from "react";
    
    const HeavyComponent = lazy(() => import("./HeavyComponent"));
    
    export default function App() {
      return (
        <Suspense fallback={<p>Loading...</p>}>
          <HeavyComponent />
        </Suspense>
      );
    }
  • The component code is not included in the main bundle.
  • Only loads when the component is about to be rendered.
  • Wrap lazy components in Suspense for a fallback UI.
    <Suspense fallback={<div>Loading page...</div>}>
      <HeavyPageComponent />
    </Suspense>
  • Works great for route-based code splitting.

Route-Based Code Splitting

  • Split code by route to load pages on demand in Next.js or React Router.
    import { lazy } from "react";
    
    const Dashboard = lazy(() => import("./pages/Dashboard"));
    const Settings = lazy(() => import("./pages/Settings"));
    const Profile = lazy(() => import("./pages/Profile"));
  • Each route is a separate chunk loaded when the user navigates.
  • Reduces the initial bundle size significantly.
    <Routes>
      <Route path="/dashboard" element={<Dashboard />} />
      <Route path="/settings" element={<Settings />} />
      <Route path="/profile" element={<Profile />} />
    </Routes>
  • Most effective optimization for large applications.
  • Combine with Suspense for loading states while pages load.

Component Splitting

  • Split large components into smaller memoized pieces.
    const Header = React.memo(() => <header>...</header>);
    const Content = React.memo(({ data }) => <main>{data}</main>);
    const Footer = React.memo(() => <footer>...</footer>);
    
    export default function Page({ data }) {
      return (
        <>
          <Header />
          <Content data={data} />
          <Footer />
        </>
      );
    }
  • Memoized children don't re-render when parent re-renders.
  • Only effective if children receive stable props.
  • Combine with useCallback to keep function props stable.
    const handleClick = useCallback(() => {
      // Handle click
    }, []);
    
    <Button onClick={handleClick} />

Performance Monitoring

  • Use Profiler API to measure component rendering time.
    import { Profiler } from "react";
    
    <Profiler id="dashboard" onRender={onRender}>
      <Dashboard />
    </Profiler>
    
    function onRender(id, phase, actualDuration) {
      console.log(`${id} (${phase}) took ${actualDuration}ms`);
    }
  • Use React DevTools Profiler to identify slow components.
  • Measure before and after optimization to confirm improvements.
  • Only optimize the slow components, not everything.
    // Profile to find slow components first
    // Then apply memo, lazy, or splitting strategically
  • Avoid premature optimization — measure real performance first.

Tip: Use lazy loading for large components and code splitting for routes to significantly reduce initial bundle size and improve page load time.

Warning: React.memo only does shallow comparison, so passing new objects or arrays as props defeats memoization — use useMemo or useCallback to keep props stable.

React LifecycleReact Portals