This is a post on how does Lazy Loading work in react, I’ll get right into how to implement it, and what happens after implementing the techniques written in React Docs and other sources.
How to Implement Lazy Loading?
Well what you do is instead of statically importing a component in react,
you dynamically import it using the lazy
method from react.
You should call it outside your component like a static import.
import "./App.css";
import { lazy, Suspense, useState } from "react";
// Dynamic Import
const Images = lazy(() => import("./components/Images"));
function App() {
const [show, setShow] = useState(false);
return (
<>
<button onClick={() => setShow(!show)}>Show</button>
{show && (
<Suspense fallback="Loading...">
<Images />
</Suspense>
)}
</>
);
}
export default App;
So what happens here? React won’t load the <Images />
component until it is rendered
so the bundle will be smaller initially thus helping the app load faster.
The <Images/>
component is a single <img/>
element with a 300 KB size image, so
initially the browser will save 300 KB from the downloaded resources while if statically imported it will load the component’s resources even if it is not rendered.
But what if I don’t want to render the component conditionally? What if I just want to
load it when it reaches the viewport or as it gets near to the viewport? We can use the Intersection Observer API for that.
Using Intersection Observer API
function App() {
const [show, setShow] = useState(false);
const ref = useRef(null);
// This is a simple implementation, some may prefer a custom hook
useEffect(() => {
const observer = new IntersectionObserver(
([entry], observerInstance) => {
if (entry.isIntersecting) {
setShow(true);
observerInstance.unobserve(entry.target);
}
},
{
rootMargin: "150px",
},
);
if (ref.current) {
observer.observe(ref.current);
}
// clean up function for when the component unmounts
return () => {
if (ref.current) observer.unobserve(ref.current);
};
}, []);
return (
<>
<div ref={ref}>
{show && (
<Suspense fallback="Loading...">
<Images />
</Suspense>
)}
</div>
</>
);
}
export default App;
As you can see we create a new IntersectionObserver , we don’t specify the ‘root’ element
because we want it to be the browser’s viewport. After that we check when the observed element/s
intersect so that we set the show
state to true and render the component. After that we stop observing the element, using the
rootMargin we increase the size of the bounding box to compute intersection earlier thus executing what
we want earlier too (it is similar to margin
in css).
We use a ref
to help detect the element we need, we use it here in a <div>
that wraps the <Images/>
component.
Is there another way to lazy load resources?
Yes, you can lazy load resources using the loading
attribute in <img/>
and <iframe/>
elements,
you won’t have to use an intersection observer for it to work, though its behaviour may differ upon
circumstance.
For example, the loading
attribute’s distance-from-viewport threshold depends on multiple factors,
and the minimum threshold is 1250px, so it will load the image initially if it wasn’t more than 1250px away
from the viewport.
You can find a demo right here .
Finally, lazy loading is a simple technique to help your app load faster initially, you should only lazy load non-critical resources and avoid overdoing it. It is one of many performance enhancements techniques that are highly recommended.