Lazy loading react components with dynamic imports and intersection observer
Tags: react, javascript
Published: 2021-08-13 06:43:38

###Lazy Loading

Lazy loading is a way by which we can load content only when they are needed. This is achieved by code-splitting, where we split our app into multiple chunks. The idea here is to serve the user with only the content they can view, and serve the other contents as and when the user visits them. 

###Route based code-splitting

For example, lets say we have a website where we have `/home`, `/profile` and `/about` routes, and `/home` is where the user first lands on. If we can compile the three routes into three bundles, we can serve them as and when the user visits the respective pages. The code for `home` route would contain only the code in `<Home />` component. And when the user visits `/about`, the content for this route will be downloaded and displayed. If we have a fairly large app with lots of routes, this would give us a significant performance gain on initial page load times.

###Component based code-splitting

 The above example describes what is a route-based code-splitting strategy. We can take this a step further with a component based code-splitting strategy. Lets say we have a heavy form component buried deep in the app which the user would rarely use. It doesn't make sense for us to add it to our main bundle and it's a perfect recipe for lazy-loading. 

### Dynamic Imports

We can achieve this in React using [Dynamic imports](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#dynamic_imports). React provides us way by which we can leverage dynamic imports with `React.lazy` and `Suspense` [From React docs](https://reactjs.org/docs/code-splitting.html#reactlazy).

Lets build our example. We have a dummy form component `<HeavyForm />`. It does nothing, but you get the idea.
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4n9djramledfcaramgz3.png)
 
And, if we want to dynamically import it, we would do something like this
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/myy1jbv8zzl6p3mi1hjq.png)

###Intersection Observer
If you run the code now, you can see the `HeavyForm` is downloaded as a separate js file. This means that `HeavyForm` was bundled as a separate chunk and it is not part of our main bundle. 

Great! But still, it is downloaded as soon as the page loads. We want this to be downloaded only when it is in the viewport, i.e when the user actually sees it. 
 
This is where [Intersection Observer](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver) comes in. IntersectionObserver lets us know if the target element is in the viewport or not. We can safely assume that if the IntersectionObserver fires, the target element is in the viewport. We can leverage this and lazily load any component when it is in the viewport.

I'm going to be using [react-intersection-observer](https://github.com/thebuilder/react-intersection-observer) library, which uses native IntersectionObserver underneath and gives us neat hooks for ease of use.

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/53nc4rqom0c7vbnmz1u5.png)

This is what the complete implementation would look like with IntersectionObserver. `react-intersection-observer` gives us `useInView` hook, which gives us a `ref` and `inView` flag. The `ref` should be attached to the target element and `inView` lets us know if the target element is in the viewport. The `threshold` option is a value between `0` and `1` indicating the percentage of element that should be visible before triggering.

Now, `<HeavyForm />` would only be downloaded when it is in the viewport.

###Conclusion

This technique can be extended to multiple routes and components for easy gains on initial page load times. Remember to strike a balance between the components you lazy-load and components that are added to the main bundle. There is a penalty of network round trip that needs to be made when requesting lazy-loaded content. 
Cheers!

You can take a look at the entire source code [here](https://github.com/sidv93/dynamic-import-with-intersection-observer)