- What is lazy loading in React?
- Benefits of lazy loading React component
- Bundling
- Code splitting
- Dynamic imports in React
- What is React lazy?
- What is the Difference Between Dynamic Import and Regular Import?
- What is React Suspense?
- Avoiding Fallback in React Suspense
- Error Handling for React Lazy
- Named exports for React components
- Route-based lazy loading in React
- Server-side code-splitting in React
- React Lazy Loading Best Practices
- Recommended Resources
- Conclusion
- Interesting Reads From Our Blogs
What is lazy loading in React?
Lazy loading in React is an optimization technique for websites or web applications. It is also known as on-demand loading because it only loads content visible on users’ screens. What does optimization mean?
Web optimization is the process of using controlled experimentations to improve the performance of a web application. To better understand how lazy loading React components help optimize a website, let’s take a look at a simple analogy.
So when a user visits a web page, instead of loading the entire page, only a portion of it renders. Then, react lazy loading delays the remaining webpage until the user scrolls to that portion of the web page.
For example, if a web page has an image that the user has to scroll down to see, you can display a placeholder. Then, you can lazy load the full image only when the user arrives at that section where the image is.
Now that we understand the concept of lazy loading, let’s go deeper into how lazy loading can be used in a React application.
In a React application, images are not the only things that can be lazy-loaded, codes can also be lazy-loaded. In fact, React has made lazy-loading some sections of web pages easier. Because in React, web pages are built in small chunks called components.
Therefore, making it easy to load an entire component and only show it to the user when they scroll to that part of the webpage. Thereby saving bandwidth and precious computing resources.
Benefits of lazy loading React component
The major benefit of lazy loading in React is performance. Loading less JavaScript code to the browser will reduce DOM load time and boost the performance of our application. Users are able to access a web page even if everything has not been loaded. Let’s take a look at some other benefits of lazy loading a React component:
- Faster initial loading: React lazy loading helps reduce the page weight, allowing for a faster initial page load time.
- Better User Experience: React lazy loading improves a user’s experience on an application. Good UX helps increase sales in business and also retain customers.
- Less bandwidth consumption: React lazy loading images help save data and bandwidth. This is particularly useful for users who do not have fast internet or large data plans.
- Preserving system resources: Lazy loading React components helps conserve server and client resources by requesting just a fraction of components.
- Reduced work for the browser: When React lazy loads images, browsers do not need to process images until they are requested when users scroll to that part of the page.
Bundling
Remember that React websites are built in chunks as components in different files. Then, they get imported to some other files to make up a web page.
Bundling is the process of following these imported files and merging them into a single file which is known as a “bundle”. You can use tools like Webpack, Rollup or Browserify. This bundle can then be included on a webpage to load an entire app at once.
If you’re using Create React App, Next.js, Gatsby, or a similar tool, you do not need to set up bundling. Webpack is set up out of the box to bundle your React application.
You’ll only need to set up bundling if you’re not using Create React App, Next.js, Gatsby, or any similar tools. You can check out the installation and getting started guides on the Webpack docs.
Let’s see React bundling in practice in the following code examples. We’ll be creating two different files with codes inside them. Then we’ll bundle them into a single and see what the code would look like in the bundled file.
Let’s create a text.js file with a function inside it that will return a dummy lorem ipsum text as a string. Then, we’ll export it so we can use it in our app.js file later.
1 2 3 4 5 6 7 8 | ``` // text.js const blogContent = () => { return "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." }; export default blogContent; ``` |
Now that both our files are ready, let’s look at our bundled file and see what the code looks like.
1 2 3 4 5 6 7 8 9 10 11 | ``` // app.js import blogContent from "./text.js" ; const str = blogContent(); console.log(str); /* Output: Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. */ ``` |
From the above code examples, you will notice that all our code from both files was merged into a single file known as the “bundled” file.
Code splitting
Bundling is great, but as your app grows, it reduces the performance of your app because your bundle grows too. And when you have a large bundle size, it impacts page load time negatively. Especially if you are including large third-party libraries.
Don’t worry if your app already has an oversized bundle. There’s a solution that can increase the performance and page load time of such an app. This solution is called code splitting.
Code-Splitting is a feature supported by bundlers like Webpack, Rollup, and Browserify (via factor-bundle. Bundlers can divide a large bundle of code into multiple smaller bundles that can be dynamically loaded at runtime.
Code-splitting helps “lazy-load” the things currently needed by the user by loading the necessary code the user needs. This avoids loading code that the user may never need.
Although this technique does not reduce the overall amount of code in your application, it can dramatically improve the performance of your app.
Dynamic imports in React
There are different ways or techniques to split code. One way to split code is to use dynamic imports, which leverage the `import()` syntax. Dynamic imports have a different syntax from the normal regular import syntax. This syntax allows webpack to automatically start the code-splitting process.
Dynamic imports return a promise which can only be used asynchronously. It will return if the promise is a fulfilled promise, and an error if it is a rejected promise.
It is important to note that this returned response from the fulfilled promise will be a function or a component.
Using our previous code examples, let us see what it looks like to dynamically import a module for an app bundled with webpack. Let us dynamically import the `blogContent` function that we previously added to our text.js file into a new file called dynamic.js
1 2 3 4 5 6 7 8 9 10 11 12 | ``` // dynamic.js import ( "./text.js" ).then(blogContent => { const str = blogContent(); console.log(str); /* Output: Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. */ }); loadDynamicImport(); ``` |
Since what dynamic import return is a promise, and as we might already have guessed. We can also use the `async await` syntax with dynamic imports, here is what it will look like in code:
1 2 3 4 5 6 7 8 9 10 11 12 13 | ``` // dynamic.js - import with async await const loadDynamicImport = async () => { const blogContent = await import ( "./text.js" ); const str = blogContent(); console.log(str); /* Output: Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. */ } loadDynamicImport(); ``` |
What is React lazy?
React lazy is a new function in react that lets you load react components lazily through code splitting without help from any additional third-party libraries. Before now, a third-party library was needed to achieve this.
We now have `React lazy()` integrated into the core react library itself. This makes it easy for us to lazy load react components.
`React lazy()` makes it easy to render react components that are imported using the dynamic import() technique. The components are rendered as regular components which automatically causes the bundle containing the component to load only when the component is rendered.
React lazy() takes a function that must call a dynamic import(). This dynamic import must return a Promise which resolves to a module with a default export containing a React component.
Without a dynamic import(), here is how we would import a regular component:
1 | import OtherComponent from './OtherComponent' ; |
We’ve already seen how to import a component dynamically in React. But what we have not seen yet is how to use components imported dynamically as regular imports:
1 | const OtherComponent = React.lazy(() => import ( './OtherComponent' )); |
What is the Difference Between Dynamic Import and Regular Import?
What then is the difference between a dynamic import() and a regular import()? The major difference between these two imports is that on dynamic import(), you import the component on reaching the expression:
1 2 3 4 5 | let OtherComponent = undefined; // Never imported. if ( false ) { OtherComponent = React.lazy(() => import ( './OtherComponent.jsx' )); } |
While on regular import(), you import the component on component call:
1 2 3 4 5 6 7 | // AB.jsx import AB from './AB.jsx' ; function App() { // imports A and B return <AB/> } |
1 2 3 4 5 6 7 8 | // AB.jsx import A from './A.jsx' ; import B from './B.jsx' ; // B is imported altough never used. function AB() { return <A/>; } |
What is React Suspense?
React Suspense is a react component that lets components “wait” for something before rendering. Today, React Suspense only supports one use case which is loading components dynamically with React lazy(). In the future, it will support other use cases like data fetching.
A component created using React lazy() is loaded only when it needs to be rendered. Therefore, you need to display some kind of placeholder content while the lazy component is being loaded . Such as a loading indicator so users know that there’s actually something loading that they need to wait for.
React Suspense component lets us show a loading indicator as a fallback prop while we’re waiting for the lazy component to load.
1 2 3 4 5 6 7 8 9 10 11 12 13 | import React, { Suspense } from 'react' ; const OtherComponent = React.lazy(() => import ( './OtherComponent' )); function MyComponent() { return ( <div> <Suspense fallback={<div>Loading...</div>}> <OtherComponent /> </Suspense> </div> ); } |
As seen in the code example above, the fallback prop accepts JSX which it renders to the screen while waiting for react lazy components inside the Suspense component to be loaded.
What Can You Use to Wrap Component Imports in Order to Load Them Lazily?
You can even wrap multiple lazy components with a single React Suspense component.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | import React, { Suspense } from 'react' ; const OtherComponent = React.lazy(() => import ( './OtherComponent' )); const AnotherComponent = React.lazy(() => import ( './AnotherComponent' )); function MyComponent() { return ( <div> <Suspense fallback={<div>Loading...</div>}> <section> <OtherComponent /> <AnotherComponent /> </section> </Suspense> </div> ); } |
Avoiding Fallback in React Suspense
We’ve seen how we can use React Suspense to improve users’ experience when lazy loading a component in React with the component passed to the fallback props. But what if we’re in a situation where the component passed as a fallback is causing a bad user experience?
As we do know, to everything that has an advantage, there is a disadvantage. And in this case, it’s the bad UX. So how do we fix this bad UI?
Let’s take a look at a code example and see what happens when we try to use the React Suspense component without passing a fallback prop since it is resulting in a bad UX which already is defeating one of the reasons for lazy loading a React component.
1 2 3 4 5 6 7 8 9 10 11 12 13 | import React, { Suspense } from 'react' ; const OtherComponent = React.lazy(() => import ( './OtherComponent' )); function MyComponent() { return ( <div> <Suspense> <OtherComponent /> </Suspense> </div> ); } |
Now let’s see what the output will look like:
Oops! But why are we getting an error? How else are we supposed to prevent the component passed to the fallback prop if it is not by removing the fallback prop in the Suspense component?
Brainstorm Solution
So let’s see why we got the above error. Then we will learn how to properly avoid the fallback prop in the Suspense component without getting the error. Additionally, we’ll see a real scenario where we might need to avoid the fallback prop in the Suspense component.
The error indicates that our code splitting is working perfectly. However, we have to specify a fallback prop to the Suspense component in order to get rid of the error above. Remember that we are trying to avoid rendering the component passed to the fallback prop.
React lazy loading can not do without the Suspense component, which also can not work with a fallback prop because we will still get the same error even if we don’t specify a Suspense component.
However, there’s no problem in programming that does not have a solution. In this scenario, we can still have a Suspense component with a fallback prop. And at the same time avoid rendering the component in the fallback prop.
Let’s take a look at a code example where we might need to use the Suspense component with a fallback prop and at the same time avoid rendering the component in the fallback prop.
Using Suspense
First, let’s create a `Loader.js` component first that we can pass as a fallback prop to the Suspense component before proceeding with the code example.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | // Loader.js const style = { height: "100vh" , display: "flex" , justifyContent: "center" , alignItems: "center" , textAlign: "center" } const Loader = () => { return ( <div style={style}> <h1>Loading...</h1> </div> ) } export default Loader; |
Now that we have our Loader component ready, let’s continue with the code example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | import React, { Suspense } from 'react'; import Tabs from './Tabs'; import Loader from './Loader'; const Comments = React.lazy(() => import('./Comments')); const Photos = React.lazy(() => import('./Photos')); function MyComponent() { const [tab, setTab] = React.useState('photos'); function handleTabSelect(tab) { setTab(tab); }; return ( <div> <Tabs onTabSelect={handleTabSelect} /> <Suspense fallback={<Glimmer />}> {tab === 'photos' ? <Photos /> : <Comments />} </Suspense> </div> ); } |
In the code example above, when a user switches the tab from ‘photos’ to ‘comments’, Comments will suspend. Then the user will see a loader until Comments have finished loading. This makes sense because the Comments component is not ready to render anything yet. React needs to keep the user experience consistent, so it has no choice but to show the Loader above.
However, sometimes this user experience is not desirable. In particular, it is sometimes better to show the user the “old” UI while the new UI is being prepared. The new `startTransition` API can make React do this without having to delete or remove the Suspense component or its props from your code.
1 2 3 4 5 | function handleTabSelect(tab) { startTransition(() => { setTab(tab); }); } |
Here, you are telling React that setting the tab to ‘comments’ is not an urgent update, but is a transition that may take some time. React will then keep the old UI in place and interactive instead of rendering the Loader component, and will switch to showing <Comments /> when it is ready.
Error Handling for React Lazy
As mentioned earlier, the import() function returns a promise when using React lazy(). This promise can be rejected due to different reasons such as network failure, file not found errors, file path errors, and so forth.
To maintain or sustain a good user experience upon failure when a lazy-loaded component fails to load, you should place an error boundary around the lazy component like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | import React, { Suspense } from "react" ; const LazyComponent1 = React.lazy(() => import ( "./OtherComponent1" )); const LazyComponent2 = React.lazy(() => import ( "./OtherComponent2" )); const LazyComponent3 = React.lazy(() => import ( "./OtherComponent3" )); const LazyComponent4 = React.lazy(() => import ( "./OtherComponent4" )); import ErrorBoundary from "./error.boundary.js" ; const MyComponent = () => ( <div> <ErrorBoundary> <Suspense fallback={<div>Loading...</div>}> <LazyComponent1 /> <LazyComponent2 /> <LazyComponent3 /> <LazyComponent4 /> </Suspense> </ErrorBoundary> </div> ); |
You can use Error Boundaries to handle errors and show a nice user experience and also manage recovery. Error Boundaries can be used anywhere above a lazy component to display an error state when there’s a network error, a file not found error, or a file path error as seen in the code example above.
Named exports for React components
React lazy() currently only supports default exports at the moment. If the module you want to import uses named exports, you can create an intermediate module that reexports these named exports as default exports so you can then be able to use it with React lazy().
This ensures that tree shaking keeps working and that you don’t pull in unused components. Let’s export a component called `MyComponent` as a named export and then re-export it as a default export.
1 2 3 | // ManyComponents.js export const MyComponent = /* ... */ ; export const MyUnusedComponent = /* ... */ ; |
There are different ways of reexporting a named export as a default export, but we won’t be going through all of them in this article.
1 2 | // MyComponent.js export { MyComponent as default } from "./ManyComponents.js" ; |
Now that our named export has been reexported as a default export, let’s use it in our React lazy().
1 2 3 | // MyApp.js import React, { lazy } from 'react' ; const MyComponent = lazy(() => import ( "./MyComponent.js" )); |
Route-based lazy loading in React
Deciding where to introduce code splitting in your app can be a bit tricky. You want to make sure you choose places that will split bundles evenly but won’t disrupt the user experience.
One approach to code-splitting React components is called route-based code-splitting, which requires applying dynamic import() to lazy load route components.
Let’s take a look at a code example of how to set up route-based code splitting into your app using libraries like React Router with React lazy().
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | import React, { Suspense, lazy } from 'react' ; import { BrowserRouter as Router, Routes, Route } from 'react-router-dom' ; const Home = lazy(() => import ( './routes/Home' )); const About = lazy(() => import ( './routes/About' )); const App = () => ( <Router> <Suspense fallback={<div>Loading...</div>}> <Routes> <Route path= "/" element={<Home />} /> <Route path= "/about" element={<About />} /> </Routes> </Suspense> </Router> ); |
Server-side code-splitting in React
React lazy() and Suspense are not yet available for server-side rendering. If you want to do code-splitting in a server-side rendered app, Loadable Components is highly recommended.
React Lazy Loading Best Practices
Keep the following best practices in mind while lazy loading a React component:
- Lazy-load components in code only when they’re not necessary for the initial functionality or features in a website or web app.
- Do not lazy load any static component that remains constant across all pages. Good examples of such components are Header and Footer components. They should be loaded in the usual way. Since they are used on every page, thus, there is no point in asynchronously loading them.
- Lazy-load only components below or beyond the user’s initial viewport.
- Use the decode() method in JavaScript to decode lazy-loading images asynchronously before adding them to the DOM.
- It is important to always set a proper error boundary to handle errors that may arise in case your components fail to load when lazy loading.
- It is important to provide a noscript alternative for components that are to be lazy-loaded for users who disable JavaScript in their browser or in cases when JavaScript is not available.
Recommended Resources
Finally, we’re at the end of the article but there’s always more to learn, so if you’d like to learn more about React lazy() and code-splitting. I’d recommend you look up the resources below:
- Code Splitting in React JS – Lazy Loading Performance Optimization:
- ReactJS: Code Splitting Explained:
Conclusion
With React lazy() and React.Suspense, code-splitting and lazy loading of React components have never been simpler.
These functions are a great way to speed up the performance of your React app and improve the overall user experience. If lazy loading in React is used appropriately, it may help you build efficient and user-friendly solutions. Additionally, you can speed up React development with CopyCat, a Figma to code plugin that helps you eliminate sprint delays and build faster than your competition. Using A.I., this Figma to React tool generates production-ready code with the click of a button.