React.lazy() must always be used with Suspense — this is a mandatory requirement. Suspense provides fallback UI while the lazy component is loading. Without Suspense, the application will throw an error.
Proper usage:
// Create lazy component
const LazyComponent = React.lazy(() => import('./LazyComponent'));
// Must wrap in Suspense
function App() {
return (
<React.Suspense fallback={<div>Loading...</div>}>
<LazyComponent />
</React.Suspense>
);
}
React.lazy() and Suspense work together to implement code splitting. Suspense is a mechanism that “suspends” component rendering until the required resources are loaded.
// ❌ WITHOUT Suspense - app will crash with error
function App() {
const LazyComponent = React.lazy(() => import('./LazyComponent'));
return (
<div>
<h1>My App</h1>
{/* Error: A React component suspended while rendering */}
<LazyComponent />
</div>
);
}
// React.lazy returns a special object
const LazyComponent = React.lazy(() => import('./Component'));
// This object "throws" a Promise on first render
// Suspense catches this Promise and shows fallback
// Without Suspense, Promise is not caught = error
const Header = React.lazy(() => import('./Header'));
const Content = React.lazy(() => import('./Content'));
const Footer = React.lazy(() => import('./Footer'));
function App() {
return (
// ✅ Can wrap multiple components
<React.Suspense fallback={<div>Loading app...</div>}>
<Header />
<Content />
<Footer />
</React.Suspense>
);
}
function Dashboard() {
// All three components will start loading in parallel
const Stats = React.lazy(() => import('./Stats'));
const Charts = React.lazy(() => import('./Charts'));
const Table = React.lazy(() => import('./Table'));
return (
<React.Suspense fallback={<DashboardSkeleton />}>
{/* Fallback shown until ALL components are loaded */}
<Stats />
<Charts />
<Table />
</React.Suspense>
);
}
function App() {
return (
<div>
{/* Each component loads independently */}
<React.Suspense fallback={<HeaderSkeleton />}>
<Header />
</React.Suspense>
<React.Suspense fallback={<MainSkeleton />}>
<MainContent />
</React.Suspense>
<React.Suspense fallback={<SidebarSkeleton />}>
<Sidebar />
</React.Suspense>
</div>
);
}
function App() {
return (
// Outer Suspense for critical components
<React.Suspense fallback={<AppLoader />}>
<Layout>
<Header />
{/* Inner Suspense for content */}
<React.Suspense fallback={<ContentLoader />}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/profile" element={<Profile />} />
</Routes>
</React.Suspense>
<Footer />
</Layout>
</React.Suspense>
);
}
// ❌ Waterfall - components load sequentially
function WaterfallLoading() {
const [showSecond, setShowSecond] = useState(false);
return (
<>
<React.Suspense fallback={<div>Loading first...</div>}>
<FirstComponent onLoad={() => setShowSecond(true)} />
</React.Suspense>
{showSecond && (
<React.Suspense fallback={<div>Loading second...</div>}>
<SecondComponent />
</React.Suspense>
)}
</>
);
}
// ✅ Parallel loading - more efficient
function ParallelLoading() {
return (
<React.Suspense fallback={<div>Loading components...</div>}>
<FirstComponent />
<SecondComponent />
</React.Suspense>
);
}
// If component fails to load, Suspense won't help
function App() {
return (
<React.Suspense fallback={<div>Loading...</div>}>
{/* If loading fails - white screen */}
<LazyComponent />
</React.Suspense>
);
}
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
// Log error
console.error('Chunk loading error:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return (
<div className="error-fallback">
<h2>Failed to load component</h2>
<button onClick={() => window.location.reload()}>
Reload Page
</button>
</div>
);
}
return this.props.children;
}
}
// Usage
function App() {
return (
<ErrorBoundary>
<React.Suspense fallback={<Loader />}>
<LazyComponent />
</React.Suspense>
</ErrorBoundary>
);
}
function createLazyWithRetry(componentImport) {
return React.lazy(() =>
componentImport().catch((error) => {
// Check if this is a chunk loading error
if (error.name === 'ChunkLoadError') {
// Try loading again
return new Promise((resolve) => {
setTimeout(() => {
resolve(componentImport());
}, 1500);
});
}
throw error;
})
);
}
// Usage
const RobustLazyComponent = createLazyWithRetry(
() => import('./HeavyComponent')
);
function RetryableErrorBoundary({ children }) {
const [hasError, setHasError] = useState(false);
const [retryCount, setRetryCount] = useState(0);
const resetError = () => {
setHasError(false);
setRetryCount(prev => prev + 1);
};
if (hasError) {
return (
<div className="error-container">
<h3>Loading Error</h3>
<p>Failed to load required resources</p>
<button onClick={resetError}>
Try Again ({retryCount})
</button>
</div>
);
}
return (
<ErrorBoundary onError={() => setHasError(true)}>
{/* Force re-mount on retry */}
<React.Fragment key={retryCount}>
{children}
</React.Fragment>
</ErrorBoundary>
);
}
function DelayedSuspense({ delay = 300, fallback, children }) {
const [showFallback, setShowFallback] = useState(false);
useEffect(() => {
const timer = setTimeout(() => {
setShowFallback(true);
}, delay);
return () => clearTimeout(timer);
}, [delay]);
return (
<React.Suspense fallback={showFallback ? fallback : null}>
{children}
</React.Suspense>
);
}
// Usage - fallback appears only if loading > 300ms
<DelayedSuspense fallback={<Spinner />}>
<LazyComponent />
</DelayedSuspense>
function ProgressiveFallback() {
const [stage, setStage] = useState(0);
useEffect(() => {
const timers = [
setTimeout(() => setStage(1), 500), // "Loading..."
setTimeout(() => setStage(2), 2000), // "Almost there..."
setTimeout(() => setStage(3), 5000), // "Taking longer..."
];
return () => timers.forEach(clearTimeout);
}, []);
const messages = [
"Loading...",
"Almost there...",
"This is taking longer than usual...",
"Please check your connection..."
];
return (
<div className="progressive-loader">
<Spinner />
<p>{messages[stage]}</p>
</div>
);
}
// ✅ Good - logically related components
<React.Suspense fallback={<DashboardSkeleton />}>
<DashboardHeader />
<DashboardStats />
<DashboardCharts />
</React.Suspense>
// ❌ Bad - unrelated components
<React.Suspense fallback={<div>Loading...</div>}>
<UserProfile />
<WeatherWidget />
<StockTicker />
</React.Suspense>
// ✅ Good - skeleton matches content
<React.Suspense fallback={<ArticleSkeleton />}>
<Article />
</React.Suspense>
// ❌ Bad - generic spinner for everything
<React.Suspense fallback={<Spinner />}>
<ComplexDashboard />
</React.Suspense>
function App() {
return (
<>
{/* Critical components without Suspense */}
<NavigationBar />
{/* Main content with Suspense */}
<React.Suspense fallback={<MainSkeleton />}>
<Routes>
<Route path="/*" element={<MainApp />} />
</Routes>
</React.Suspense>
{/* Non-critical components with separate Suspense */}
<React.Suspense fallback={null}>
<Analytics />
<ChatWidget />
</React.Suspense>
</>
);
}
React.lazy() and Suspense are a powerful combination for load optimization, but require proper usage to ensure good user experience.
Want more interview preparation articles? Subscribe to EasyAdvice, bookmark the site and improve every day 💪