How to set up lazy loading components in React
As front-end application’s bundle size increases, developers started to investigate to find more effective ways to load bundles to client faster way. Code-splitting and lazy loading is a way to dramatically decreasing initial loading time to clients.
There are some strategies to split your javascript codes.
- Route base splitting
- Component base splitting
- Library base splitting
To achieve these code splitting and lazy loading strategies, 4 different libraries are investigated. Let’s dig into libraries one by one.
custom-component.js will be used in further examples
/* custom-compnent.js */
import React, { useEffect } from "react";const CustomComponent = ({ label }) => {
useEffect(() => {
console.log(`${label} created`);
return () => console.log(`${label} destroyed`);
}, []);return <div>{label}</div>;
};export default CustomComponent;
React.lazy
By the version of 16.6, React has built in support for lazy loading components. React.lazy function takes a promise based function and returns it.
- export your components default (here our CustomComponent). That library does not support named exports yet.
- Call const LazyLoadedComponent = React.lazy(() => import(‘./custom-component.js’)
- Use <LazyLoadedComponent />
in the example below, i used a promise and a timeout to show loading effect
<Suspense /> is a react component which has fallback prop that takes any react component. By using that component, you can show loading indicator.
import React, { Suspense } from "react";
import ReactDOM from "react-dom";/* wait 100 ms to render component */
const CustomComponent = React.lazy(
() =>
new Promise((resolve, reject) =>
setTimeout(() => resolve(import("./custom-component")), 100)
)
);/* wait 500 ms to render component */
const CustomComponent2 = React.lazy(
() =>
new Promise((resolve, reject) =>
setTimeout(() => resolve(import("./custom-component")), 5000)
)
);function App() {
return (
<>
<Suspense fallback={<div>Loading</div>}>
<CustomComponent label="Component 1" />
<CustomComponent2 label="Component 2" />
</Suspense>
</>
);
}
Wrapping all your custom components into one suspense as above example will cause your ui to show loading indicator until longest time to uploaded component to be uploaded. In above example, Component 1 will be created but wont be shown until Component 2 is loaded.
A working example on codesandbox can be found here
To overcome this issue, you can wrap your lazy loaded components into multiple <Suspense /> components.
<>
<Suspense fallback={<div>Loading</div>}>
<CustomComponent label="Component 1" />
</Suspense>
<Suspense fallback={<div>Loading</div>}>
<CustomComponent2 label="Component 2" />
</Suspense>
</>
A working example on codesandbox can be found here
/* route base splitting */
const DashboardPage = React.lazy(() => import('../pages/dashboard'));
const SettingsPage = React.lazy(() => import('../pages/settings'));<Route>
<DashboardPage />
<SettingsPage />
</Route>
Disadvantage: You must import all page components one by one manually
@loadable/components
If you have an SSR, then React.lazy will not fit your needs, because it does not support SSR. But that library’s power is not about only having SSR, but also has some nice features like prefetching, webpack hints and library splitting. So, what that library offers you is,
- SSR
- Using webpack hints for splitting
- Able to split your chunks according to libraries
- Allowing you to write a generic function for dynamic import such as import (`${component}`)
- Usage of <Suspence /> (Not needed, Optional)
- prefetching
Usage is similiar to React.lazy.
import React, { Suspense } from "react";
import ReactDOM from "react-dom";
import loadable, { lazy } from "@loadable/component";const CustomComponent = lazy(
() =>
new Promise((resolve, reject) =>
setTimeout(() => resolve(import("./custom-component")), 100)
)
);const CustomComponent2 = lazy(
() =>
new Promise((resolve, reject) =>
setTimeout(() => resolve(import("./custom-component")), 5000)
)
);function App() {
return (
<>
<Suspense fallback={<div>Loading</div>}>
<CustomComponent label="Component 1" />
<CustomComponent2 label="Component 2" />
</Suspense>
</>
);
}
In the above example, just replacing React.lazy with lazy function coming from @loadable/component import will be enough.
Let’s remove <Suspense /> component, and use fallback prop in @loadable/component . The only difference between <Suspense /> usage and fallback usage is, we do not use lazy function from @loadable/component but loadable.
import React from "react";
import ReactDOM from "react-dom";
import loadable from "@loadable/component";const CustomComponent1 = loadable(
() =>
new Promise((resolve, reject) =>
setTimeout(() => resolve(import("./custom-component")), 100)
),
{
fallback: <div>Loading...</div>
}
);const CustomComponent2 = loadable(
() =>
new Promise((resolve, reject) =>
setTimeout(() => resolve(import("./custom-component")), 5000)
),
{
fallback: <div>Loading...</div>
}
);function App() {
return (
<>
<CustomComponent1 label="Compnent 1" />
<CustomComponent2 label="Component 2" />
</>
);
}
Prefetching feature has two types of usage.
To load component when browser is idle, webpack’s webpackPrefetch and webpackPreload hints could be used.
const CustomComponent = loadable(() =>
import(/* webpackPrefetch: true */ './custom-component.js'),
)
Another usage is triggering prefetch manually. There is preload() function which is called when component needed to be shown.
import React from "react";
import ReactDOM from "react-dom";
import loadable from '@loadable/component'const CustomComponent = loadable(() => import('./custom-component.js'))function App() {
const [show, setShow] = useState(false)
return (
<div>
<a
onMouseOver={() => CustomComponent.preload()}
onClick={() => setShow(true)}>
Show me
</a>
{show && <Infos />}
</div>
)
}
To use preLoad in SSR, you must use webpack hints. This library does not support preLoad feature in SSR for now.
This library does not support timeout or delay props natively. To use these features, it’s recommended to use some 3rd party libraries which can be found on its github repo.
import loadable from '@loadable/component'const LazyLoadPage = loadable({page} => import(`./${page}`))<Route>
<LazyLoadPage page="Dashboard" />
<LazyLoadPage page="Setting" />
</Route>
By using full dynamic loading feature, it’s easy to create a async routes
react-loadable
react-loadable has huge amount of features from SSR to custom rendering. It’s usage is similar to @loadable/components with extra features.
What it offers you,
- built in delay, timeout features
- custom rendering component instead of imported one
- SSR
- prefetching
import Loadable from "react-loadable";const CustomComponent = Loadable({
loader: () =>
new Promise((resolve, reject) => {
setTimeout(() => resolve(import("./custom-component")), 2000);
}),
loading: ({ pastDelay }) => (pastDelay ? <div>Loading...</div> : null),
delay: 50
});const CustomComponent2 = Loadable({
loader: () =>
new Promise((resolve, reject) => {
setTimeout(() => resolve(import("./custom-component")), 5000);
}),
loading: () => <div>Loading...</div>
});const ErrorCustomComponent = Loadable({
loader: () =>
new Promise((resolve, reject) => {
setTimeout(() => reject(import("./custom-component")), 200);
}),
loading: ({ error }) =>
!error ? <div>Loading...</div> : <div>Component could not be loaded!</div>
});const TimeoutComponent = Loadable({
loader: () =>
new Promise((resolve, reject) => {
setTimeout(() => resolve(import("./custom-component")), 2000);
}),
loading: ({ timedOut }) =>
timedOut ? <div>Taking too long...</div> : <div>Loading...</div>,
timeout: 50
});function App() {
return (
<>
<CustomComponent label="Component 1" />
<CustomComponent2 label="Component 2" />
<ErrorCustomComponent label="Component 3" />
<TimeoutComponent label="Component 4" />
</>
);
}
Although this library has lots of features and stars, latest remarkable commits were made more than 1 year ago. Although there is any warning on github repository, It may not be maintained anymore. So be careful while using it.
react-loadable-visibility
react-loadable-visibility is a wrapper component built top on react-loadable and @loadable/component libraries to load components when they are on viewport. It does it with InterSectionObserver API. It has polyfill but author of that library has some doubts about its performance.
What does this library offers you,
- Lazy load your components if they are on viewport at screen.
- easy to use with using only loadableVisibility function.
- You can still use your react-loadable & @loadable/component configuration
Meaningful if your page is too long
import loadableVisibility from "react-loadable-visibility/loadable-components";const LoadableComponent = loadableVisibility(() => import("./custom-component"), {
fallback: () => <div>Loading...</div>
});export default function App() {
return <LoadableComponent />;
}
You can find working examples on codesandbox
- React.lazy with one suspense, https://codesandbox.io/s/reactlazy-one-suspense-example-giy9c
- React.lazy with multiple suspense, https://codesandbox.io/s/reactlazy-multiple-suspense-example-hgz4u
- React.loadable, https://codesandbox.io/s/react-loadable-example-7s5mu
- @loadable/component, https://codesandbox.io/s/loadablecomponent-example-th1xt
If you’re using Slack, I’d recommend checking out the Actioner app directory. There are apps specifically designed to make a developer’s life easier — some of which I’ve had the pleasure to contribute to.
If you like my articles, you can support me by clapping and following me.
I’m also on linkedin, all invitations are welcome.