Lazy Loading, Suspense and Error Boundary in React Explained

Lazy Loading and Suspense

Lazy loading and suspense both could have been separate concepts and thus it seems the explanations should be separate. But in real world apps most of the time you will see they works hand-in-hand. So, this is why I am covering both under one heading.

Lazy loading in React enables you to break your code into smaller chunks, loading only the necessary code for a specific part of your application when it's needed. This reduces the initial load time, making your application more efficient and responsive.

The components you want to be under lazy loading concept are often called dynamic components as they are loaded conditionally at runtime, improving performance by reducing the initial bundle size.

Use React.lazy() to import the component where you want lazy loading to be enabled. This is where the concept of suspense comes into play. Because they work hand-in-hand. before I give you a demonstration with an example about the usage of lazy(), grasp the concept of suspense.

Suspense

Suspense in React is a component that allows you to handle the loading of asynchronous content, like lazy-loaded components or data, by showing a fallback UI while the content is being loaded. A very common example of using suspense that shows the usage of suspense:

<Suspense fallback={<div>Loading...</div>}>
  <LazyComponent />
</Suspense>

Here, Suspense shows the fallback (like "Loading...") while LazyComponent is being loaded. Once the component is ready, it replaces the fallback with the actual content.

Lazy Loading Component with Suspense

import React, { Suspense } from 'react';

// Lazy load the component
const LazyComponent = React.lazy(() => import('./LazyComponent'));

function App() {
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <LazyComponent />
      </Suspense>
    </div>
  );
}

export default App;

Applying React.lazy() to import a component makes that component lazy loading component. This means the component won’t be loaded during the initial render of the app. Instead, it will only load when it's needed—such as when the user navigates to it or a certain condition is met. This helps improve performance by reducing the amount of code loaded upfront.

Suspense in Data Fetching

You can use Suspense to handle various asynchronous operations, such as data fetching or any other task that requires waiting for a resource. By showing fallback content (like a loading spinner) while data is being fetched or an async operation is in progress, Suspense helps manage the loading state effectively until the process completes, ensuring a smooth user experience.

Here is an example of data fetching:

import React, { Suspense } from 'react';
import fetchData from './dataFetcher'; // Some data-fetching logic

const resource = fetchData(); // Suspends component until data is ready

function DataComponent() {
  const data = resource.read(); // Triggers fallback if data isn’t ready
  return <div>{data}</div>;
}

function App() {
  return (
    <Suspense fallback={<div>Loading data...</div>}>
      <DataComponent />
    </Suspense>
  );
}

export default App;

Error Boundary

An Error Boundary in React is a special component that catches JavaScript errors anywhere in its child component tree and prevents them from crashing the entire application. Instead of the whole app breaking, it allows you to display a fallback UI (like an error message) when an error occurs.The most important point here is it catches errors in child component tree.

In React, Error Boundaries must be implemented using class components because currently, there’s no direct way to create an error boundary with functional components using hooks. However, you can use third-party libraries like react-error-boundary to achieve error boundaries in functional components.

So, install the npm module: npm install react-error-boundary

Here is an example of using Error Boundary in react:

import React from 'react';
import { ErrorBoundary } from 'react-error-boundary';

// Fallback UI for error
function ErrorFallback({ error, resetErrorBoundary }) {
  return (
    <div role="alert">
      <p>Something went wrong:</p>
      <pre>{error.message}</pre>
      <button onClick={resetErrorBoundary}>Try again</button>
    </div>
  );
}

// Component that throws an error
function BuggyComponent() {
  throw new Error('I crashed!');
  return <div>This won't render.</div>;
}

// Main App component with functional error boundary
function App() {
  return (
    <div>
      <h1>My React App</h1>
      <ErrorBoundary
        FallbackComponent={ErrorFallback}
        onReset={() => {
          // Reset the state or do any cleanup when retrying
        }}
      >
        <BuggyComponent />
      </ErrorBoundary>
    </div>
  );
}

export default App;

When an error occurs in BuggyComponent, the error boundary catches it and displays the ErrorFallback Component. ErrorFallback displays the error message and a "Try again" button. The resetErrorBoundary function is triggered when the user clicks "Try again". resetErrorBoundary attempts to re-render the child components of the ErrorBoundary that previously threw an error. If BuggyComponent is fixed or the error is resolved, it should render successfully on the next attempt.

0
Subscribe to my newsletter

Read articles from Abeer Abdul Ahad directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Abeer Abdul Ahad
Abeer Abdul Ahad

I am a Full stack developer. Currently focusing on Next.js and Backend.