What is React.lazy() and what is it used for?

👨‍💻 Frontend Developer 🟡 Often Asked 🎚️ Hard
#React

Quick Answer

React.lazy() — is a function that enables dynamic loading of React components. It implements the “lazy loading” pattern and automatically splits application code into separate bundles (code splitting).

Basic usage:

// Instead of regular import
import HeavyComponent from './HeavyComponent';
 
// Use dynamic import
const HeavyComponent = React.lazy(() => import('./HeavyComponent'));
 
// Must wrap in Suspense
function App() {
  return (
    <React.Suspense fallback={<div>Loading...</div>}>
      <HeavyComponent />
    </React.Suspense>
  );
}

Detailed Answer

React.lazy() was introduced in React 16.6 and provides a built-in way for code splitting at the component level. This allows loading components only when they are actually needed, reducing the initial bundle size.

How React.lazy() Works

Basic Mechanics

// React.lazy takes a function that returns a Promise
const LazyComponent = React.lazy(() => import('./LazyComponent'));
 
// import() returns a Promise that resolves toa& module
// The module must export a React component as default

Component Requirements

// LazyComponent.jsx
// ✅ Correct: default export
export default function LazyComponent() {
  return <div>I load lazily!</div>;
}
 
// ❌ Incorrect: named export
export const LazyComponent = () => {
  return <div>This won't work</div>;
};
 
// Workaround for named exports
const LazyComponent = React.lazy(() =>
  import('./components').then(module => ({
    default: module.NamedComponent
  }))
);

Using with Suspense

Required Wrapper

function App() {
  return (
    <div>
      {/* ❌ Error: LazyComponent must be inside Suspense */}
      <LazyComponent />
      
      {/* ✅ Correct: use Suspense */}
      <React.Suspense fallback={<div>Loading...</div>}>
        <LazyComponent />
      </React.Suspense>
    </div>
  );
}

Advanced Suspense Usage

// Can wrap multiple lazy components
function Dashboard() {
  return (
    <React.Suspense fallback={<DashboardSkeleton />}>
      <UserProfile />
      <Analytics />
      <RecentActivity />
    </React.Suspense>
  );
}
 
// Nested Suspense for different granularity
function App() {
  return (
    <React.Suspense fallback={<AppLoader />}>
      <Header />
      <React.Suspense fallback={<ContentLoader />}>
        <MainContent />
      </React.Suspense>
      <Footer />
    </React.Suspense>
  );
}

Practical Examples

1. Route-based Splitting

import { BrowserRouter, Routes, Route } from 'react-router-dom';
 
// Lazy load pages
const Home = React.lazy(() => import('./pages/Home'));
const About = React.lazy(() => import('./pages/About'));
const Dashboard = React.lazy(() => import('./pages/Dashboard'));
const Profile = React.lazy(() => import('./pages/Profile'));
 
function App() {
  return (
    <BrowserRouter>
      <React.Suspense fallback={<PageLoader />}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/about" element={<About />} />
          <Route path="/dashboard" element={<Dashboard />} />
          <Route path="/profile/:id" element={<Profile />} />
        </Routes>
      </React.Suspense>
    </BrowserRouter>
  );
}

2. Conditional Component Loading

const AdminPanel = React.lazy(() => import('./AdminPanel'));
 
function App() {
  const [user, setUser] = useState(null);
  
  return (
    <div>
      {user?.isAdmin && (
        <React.Suspense fallback={<div>Loading panel...</div>}>
          <AdminPanel user={user} />
        </React.Suspense>
      )}
    </div>
  );
}

3. Loading Heavy Libraries

// Component with heavy charting library
const ChartComponent = React.lazy(() => import('./ChartComponent'));
 
function Analytics() {
  const [showChart, setShowChart] = useState(false);
  
  return (
    <div>
      <button onClick={() => setShowChart(true)}>
        Show Chart
      </button>
      
      {showChart && (
        <React.Suspense fallback={<ChartSkeleton />}>
          <ChartComponent data={analyticsData} />
        </React.Suspense>
      )}
    </div>
  );
}

Error Handling

Error Boundaries with Lazy Components

class ErrorBoundary extends React.Component {
  state = { hasError: false };
  
  static getDerivedStateFromError(error) {
    return { hasError: true };
  }
  
  componentDidCatch(error, errorInfo) {
    console.error('Component loading error:', error, errorInfo);
  }
  
  render() {
    if (this.state.hasError) {
      return <div>Failed to load component</div>;
    }
    
    return this.props.children;
  }
}
 
// Usage
function App() {
  return (
    <ErrorBoundary>
      <React.Suspense fallback={<Loader />}>
        <LazyComponent />
      </React.Suspense>
    </ErrorBoundary>
  );
}

Retry Mechanism

function LazyComponentWithRetry(componentPath) {
  return React.lazy(() =>
    import(componentPath).catch(() => {
      // Reload page on chunk loading error
      window.location.reload();
      // Or retry
      return new Promise((resolve) => {
        setTimeout(() => {
          resolve(import(componentPath));
        }, 1500);
      });
    })
  );
}
 
const MyComponent = LazyComponentWithRetry('./MyComponent');

Advanced Patterns

1. Component Preloading

// Preload function
const preloadComponent = (component) => {
  component._init();
};
 
// Create lazy component
const HeavyModal = React.lazy(() => import('./HeavyModal'));
 
// Preload on hover
function App() {
  const [showModal, setShowModal] = useState(false);
  
  return (
    <div>
      <button
        onMouseEnter={() => preloadComponent(HeavyModal)}
        onClick={() => setShowModal(true)}
      >
        Open Modal
      </button>
      
      {showModal && (
        <React.Suspense fallback={<ModalLoader />}>
          <HeavyModal onClose={() => setShowModal(false)} />
        </React.Suspense>
      )}
    </div>
  );
}

2. Progressive Loading

// Component with progress bar
function ProgressiveSuspense({ children }) {
  const [progress, setProgress] = useState(0);
  
  useEffect(() => {
    const timer = setInterval(() => {
      setProgress(prev => Math.min(prev + 10, 90));
    }, 100);
    
    return () => clearInterval(timer);
  }, []);
  
  return (
    <React.Suspense
      fallback={
        <div>
          <div>Loading... {progress}%</div>
          <ProgressBar value={progress} />
        </div>
      }
    >
      {children}
    </React.Suspense>
  );
}

3. Dynamic Imports with Parameters

// Function for dynamic locale loading
const loadLocale = (locale) => {
  return React.lazy(() =>
    import(`./locales/${locale}.js`).then(module => ({
      default: module.LocaleProvider
    }))
  );
};
 
function App() {
  const [locale, setLocale] = useState('en');
  const LocaleProvider = useMemo(() => loadLocale(locale), [locale]);
  
  return (
    <React.Suspense fallback={<div>Loading language...</div>}>
      <LocaleProvider>
        <MainApp />
      </LocaleProvider>
    </React.Suspense>
  );
}

Performance Optimization

1. Grouping Lazy Components

// Instead of many small lazy imports
const Button = React.lazy(() => import('./Button'));
const Input = React.lazy(() => import('./Input'));
const Select = React.lazy(() => import('./Select'));
 
// Better to group in one module
const FormComponents = React.lazy(() => import('./FormComponents'));
// FormComponents exports { Button, Input, Select }

2. Code Splitting Strategies

// By functionality
const AdminFeatures = React.lazy(() => import('./features/admin'));
const UserFeatures = React.lazy(() => import('./features/user'));
 
// By bundle size
const LargeTable = React.lazy(() => 
  import(/* webpackChunkName: "large-table" */ './LargeTable')
);
 
// By loading priority
const CriticalComponent = React.lazy(() =>
  import(/* webpackPreload: true */ './CriticalComponent')
);
 
const OptionalComponent = React.lazy(() =>
  import(/* webpackPrefetch: true */ './OptionalComponent')
);

Limitations and Pitfalls

1. Only for Default Exports

// ❌ Doesn't work directly with named exports
const { Component } = React.lazy(() => import('./module'));
 
// ✅ Need to transform
const Component = React.lazy(() =>
  import('./module').then(module => ({
    default: module.Component
  }))
);

2. Doesn’t Work with Server-Side Rendering

// For SSR use loadable-components or similar solutions
import loadable from '@loadable/component';
 
const LazyComponent = loadable(() => import('./Component'));

3. Suspense is Required

// ❌ Forgot Suspense = error
<LazyComponent />
 
// ✅ Always wrap in Suspense
<React.Suspense fallback={<div>Loading...</div>}>
  <LazyComponent />
</React.Suspense>

Best Practices

  1. Split by routes — most effective approach
  2. Group related components — avoid too fine granularity
  3. Use meaningful fallbacks — show skeletons instead of spinners
  4. Preload critical components — improve UX
  5. Handle loading errors — network can be unstable
  6. Test with slow internet — check real user experience

React.lazy() — is a powerful tool for optimizing React application performance that, when used correctly, significantly improves initial loading time.


Want more interview prep articles? Subscribe to EasyAdvice, bookmark the site, and improve every day 💪