Loading components dynamically is a technique that can replace writing import
for many components. Rather than declaring every possible component that can be used, you can use a dynamic value for the path of a component.
You can additionally use lazy-loading to serve the bundle of code that is necessary to the end-user at that particular moment. A smaller bundle size for the end-user should result in performance improvements.
React 16.6.0+ provides React.lazy
and React.Suspsense
to support lazy-loading React components. Instead of import
ing all the components, lazy-loading will allow you to only import
additional components when they are needed.
In this article, you will explore the concepts of how to load components dynamically. You will also explore the concepts of how to load components on-demand.
To complete this tutorial, you’ll need:
No local development is required.
CodeSandbox examples are provided for further experimentation.
Reddit is a website with multiple subreddits for different topics. Each subreddit follows the pattern of having an r/
prefix. Assume you are developing an application that displays views for three subreddits: r/reactjs
, r/learnreactjs
, and r/javascript
.
Suppose you are showing a different component depending on a property, subredditsToShow
:
import React from 'react';
import shortid from 'shortid';
import LearnReactView from './views/learnreactView';
import ReactView from './views/reactView';
import JavaScriptView from './views/javascriptView';
import NullView from './views/NullView';
export default function App({ subredditsToShow }) {
const subredditElementList = subredditsToShow.map(
subreddit => {
switch (subreddit) {
case 'reactjs':
return <ReactView key={shortid.generate()} />;
case 'learnreactjs':
return (
<LearnReactView key={shortid.generate()} />
);
case 'javascript':
return (
<JavaScriptView key={shortid.generate()} />
);
default:
return (
<NullView key={shortid.generate()}>
{`"r/${subreddit}" - not implemented`}
</NullView>
);
}
}
);
return <div>{subredditElementList}</div>;
}
A live example of this code is available on CodeSandbox.
Note: This example uses shortid
to generate unique keys. As of this revision, shortid
is now deprecated and nanoid
is the recommended alternative.
In this example, subredditsToShow
is defined in index.js
as:
const subredditsToShow = [
'reactjs',
'learnreactjs',
'pics',
'reactjs',
'learnreactjs',
'svelte',
'javascript',
'learnreactjs'
];
When running this application, you will observe:
Outputr/reactjs
r/learnreactjs
"r/pics" - not implemented
r/reactjs
r/learnreactjs
"r/svelte" - not implemented
r/javascript
r/learnreactjs
pics
and svelte
are not implemented. There is no case
to handle them, and there is no separate view component for that subreddit in your application. The NullView
is displayed for these subreddits.
This switch/case
approach works well for a handful of subreddits. However, handling additional subreddits will require you to:
You can prevent those issues by loading components dynamically per subreddit and removing the switch statement, as shown below, using useEffect
and useState
:
import React, { lazy, useEffect, useState } from 'react';
import shortid from 'shortid';
const importView = subreddit =>
lazy(() =>
import(`./views/${subreddit}View`).catch(() =>
import(`./views/NullView`)
)
);
export default function App({ subredditsToShow }) {
const [views, setViews] = useState([]);
useEffect(() => {
async function loadViews() {
const componentPromises =
subredditsToShow.map(async subreddit => {
const View = await importView(subreddit);
return <View key={shortid.generate()} />;
});
Promise.all(componentPromises).then(setViews);
}
loadViews();
}, [subredditsToShow]);
return (
<React.Suspense fallback='Loading views...'>
<div className='container'>{views}</div>
</React.Suspense>
);
}
A live example of this code is available on CodeSandbox.
Let’s break down the code above.
importView
imports a view dynamically. It returns a NullView
(Null object pattern) for an unmatched subreddit
.views
to render after you finished importing in useEffect
.loadViews
inside useEffect
imports views and stores them in the state with setViews
.views
are loaded.This concludes load components dynamically with strings. You will explore a more advanced example next.
Let’s consider a situation where you load a different “view” dynamically by matching against a data property.
The Reddit API exposes a JSON response for search results. Here is an example of a response when searching for “react hooks”;
[seconary_label Output]
{
"data": {
"children": [
{
"data": {
"subreddit": "reactjs",
"title": "New tutorial for React hook",
"url": "..."
}
},
{
"data": {
"subreddit": "javascript",
"title": "React Hook Form",
"url": "..."
}
},
{
"data": {
"subreddit": "Frontend",
"title": "React hook examples",
"url": "..."
}
}
]
}
}
You can handle different subreddit
s by loading only views you have implemented:
import React, { lazy, useEffect, useState } from 'react';
import shortid from 'shortid';
const importView = subreddit =>
lazy(() =>
import(`./views/${subreddit}View`).catch(() =>
import(`./views/NullView`)
)
);
const searchSubreddit = async query =>
fetch(
`https://www.reddit.com/search.json?q=${query}`
).then(_ => _.json());
export default function App({ subredditsToShow }) {
const [views, setViews] = useState([]);
const extractData = response =>
response.data.children.map(({ data }) => data);
useEffect(() => {
async function loadViews() {
const subredditsToShow = await searchSubreddit(
'react hooks'
).then(extractData);
const componentPromises = subredditsToShow.map(
async data => {
const View = await importView(data.subreddit);
return (
<View key={shortid.generate()} {...data} />
);
}
);
Promise.all(componentPromises).then(setViews);
}
loadViews();
}, [subredditsToShow]);
return (
<React.Suspense fallback='Loading views...'>
<div className='container'>{views}</div>
</React.Suspense>
);
}
A live example of this code is available on CodeSandbox.
The differences from the previous section are:
data
, instead of the subreddit
string.<View key={shortid.generate()} {...data} />
Each view now gets a copy of the data
as a prop.
Here is an example of reactjsView
. It is a view designed specifically for the reactjs
subreddit:
import React from 'react';
import Layout from './Layout';
import styled, { css } from 'styled-components';
const Container = styled.article`
display: flex;
flex-direction: column;
`;
export default ({ subreddit, title, url }) => (
<Layout
css={css`
&:hover {
background-color: papayawhip;
}
`}
>
<Container>
<h3>{title}</h3>
<p>{`r/${subreddit}`}</p>
<a href={url}>-> Visit the site</a>
</Container>
</Layout>
);
Here is an example of reactjsView
. It is a view designed specifically for the javascript
subreddit:
import React from 'react';
import Layout from './Layout';
import styled, { css } from 'styled-components';
const Container = styled.article`
display: flex;
flex-direction: row;
background-color: rgba(0, 0, 0, 0.1);
padding: 2rem;
& > * {
padding-left: 1rem;
}
`;
export default ({ subreddit, title, url }) => (
<Layout
css={css`
background-color: papayawhip;
`}
>
<Container>
<h4>{title}</h4>
<p>({`r/${subreddit}`})</p>
<a href={url}>-> Visit the site</a>
</Container>
</Layout>
);
Both of these views can use subreddit
, title
, and url
provided by the data
object.
This concludes loading components dynamically with objects.
In the previous examples, you have loaded components automatically without a performance improvement.
You can improve this by sending JavaScript only when needed when a user performs an action.
Suppose that you need to show different types of charts for the following data:
const data = [
{
id: 'php',
label: 'php',
value: 372,
color: 'hsl(233, 70%, 50%)'
},
{
id: 'scala',
label: 'scala',
value: 363,
color: 'hsl(15, 70%, 50%)'
},
{
id: 'go',
label: 'go',
value: 597,
color: 'hsl(79, 70%, 50%)'
},
{
id: 'css',
label: 'css',
value: 524,
color: 'hsl(142, 70%, 50%)'
},
{
id: 'hack',
label: 'hack',
value: 514,
color: 'hsl(198, 70%, 50%)'
}
];
You can load the site faster without sending unused JavaScript and load charts only when needed:
import React, { lazy, useState } from 'react';
import shortid from 'shortid';
const importView = chartName =>
lazy(() =>
import(`./charts/${chartName}`)
.catch(() => import(`./charts/NullChart`))
);
const data = [ ... ];
const ChartList = ({ charts }) =>
Object.values(charts).map(Chart => (
<Chart key={shortid.generate()} data={data} />
));
export default function App() {
const [charts, setCharts] = useState({});
const addChart = chartName => {
if (charts[chartName]) return;
const Chart = importView(chartName);
setCharts(c => ({ ...c, [chartName]: Chart }));
};
const loadPieChart = () => addChart('Pie');
const loadWaffleChart = () => addChart('Waffle');
return (
<main>
<section className="container">
<button disabled={charts['Pie']}
onClick={loadPieChart}>
Pie Chart
</button>
<button disabled={charts['Waffle']}
onClick={loadWaffleChart}>
Waffle Chart
</button>
</section>
<section className="container">
<React.Suspense fallback="Loading charts...">
<div className="row">
<ChartList charts={charts} />
</div>
</React.Suspense>
</section>
</main>
);
}
A live example of this code is available on CodeSandbox.
importView
is the same as it was in previous examples except for the component location.ChartList
iterates an object where the name is a chart name and the value is the imported component.App
state, charts
, is an object to track components that are already loaded.addChart
imports a chart by name dynamically and add to the charts
state, which is what you render.loadPieChart
and loadWaffleChart
are convenience methods and you can memoize with useMemo.return
renders two buttons, and you need to wrap charts with Suspense
.src/charts/Pie.js
and src/charts/Waffle.js
are only loaded when the user clicks on the respective buttons.
This concludes loading components on-demand with React.lazy
and React.Suspense
.
In this article, you were introduced to loading components dynamically and loading components on-demand. These techniques can help improve maintenance and performance.
If you’d like to learn more about React, check out our React topic page for exercises and programming projects.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!