Code-Splitting: Get a Better Performance When Developing With React

This post covers React Suspense and React Lazy, which can be used to split code, fetch data and set the order at which components are rendered—giving you total control of your project.


This content originally appeared on Telerik Blogs and was authored by Chinedu Imoh

This post covers React Suspense and React Lazy, which can be used to split code, fetch data and set the order at which components are rendered—giving you total control of your project.

One of the problems developers face when it comes to web performance is rendering unused elements to the DOM. This increases the website’s load time due to the site having to download all the elements needed before it shows anything to the user. The more elements it needs, the longer it takes to load, which is the major problem many websites face today, whereby many users give up even when trying to load the page.

A website’s performance determines a lot about what the user will be experiencing, the increase in visits to the website, and getting good feedback. In this post, we will be covering how to achieve better performance with React.

We will be covering the following in this post:

As we go, we’ll build a simple app to help us understand everything in practice.

Prerequisites

To follow with this post, you will need to have:

  • A basic understanding of React
  • Node installed
  • A text editor

What Is React JS?

React is a JavaScript library for building fast and interactive user interfaces; it was developed at Facebook in 2011 and is currently the most popular JavaScript library for building user interfaces.

React uses component-based development—features are broken down into components that are reusable later in other parts of the app. A typical React project must have at least one component, which is the root component, usually called App.js in most React projects, but you can also change the name to suit your needs.

Let’s begin with creating a React project. Input the following command into your terminal.

npx create-react-app performance-checker

Now that we have created our React project, let’s install the packages we will be using. Here is the list of the packages we will be using and their functions:

  • React Router: React Router as a package is composed of navigational components used to navigate through the entire website. It’s used to assign pages in a React project.
  • Styled-components: It’s used to write CSS in JS in the form of components that can be reused in all the project parts. Styled components use SASS format syntax to create a component of a CSS style.

Input the following command to install the packages.

npm i react-router-dom styled-components

What Is Code-Splitting?

When building a React app, the whole project is compiled into a build.js file used to render the website. In this instance, content that is not needed is downloaded, making the bundle file large with a long download time; this increases the website’s load time.

Code-splitting is splitting the bundle file into chunks based on the user’s needs or what the user is interested in seeing. This idea brings about a decrease in the website’s load time since users will need to download a smaller bundle file, giving users a better experience.

Web Performance Optimization and How React Handles It

Web performance optimization is a web development scenario for making websites faster, not for the initial loading but for the user interaction and other loadings. Broadly it involves measuring performance and recording metrics of how quickly a website loads.

It’s heavily influenced by psychology and user perception about loading. Regardless of how slowly a website loads, if the user feels the website loads fast, then it loads fast. An essential part of improving web performance includes improving the perceived performance, which is all about creating a perception that the website is loading fast.

When you run a project on React, it bundles the whole pages into a bundle.js file, after which the DOM starts rendering the website content. Sometimes things can get more tiring when the project is enormous and it has to download all the bundle files at once. For that reason, code-splitting was introduced in React as a mechanism to split bundle files into chunks based on the page the user requires; this reduces the size of files to be downloaded before rendering, which improves the load time.

What Is React Suspense?

React Suspense can be seen as a way to pause component rendering while data is fetched. It helps communicate that the data needed for rendering is not ready, rendering a fallback component in place while the data loads.

When the network is slow or the page is not loading, it gives the developer the ability to show a placeholder that avoids disrupting the user view.

React Suspense is used in three different ways:

  • In the routing level
  • While fetching data
  • In the component level

React Suspense in Routing Level

When setting up a route for a React project, React Suspense can be used with React Lazy to split the bundle size per page. We will be using React Router to set up the route to make this possible, and we will use React Lazy and React Suspense to split the code.

Before we can start the routing, we need to put some things in place; firstly, we have to create a folder inside the src folder with the name pages, which will contain all the page components.

mkdir pages

Inside the folder, create a file named home.jsx and paste the following boilerplate code inside.

touch home.jsx

import React from 'react'

const Home = () => {
    return (
        <>
          Hello
        </>
    )
}
export default Home;

Next, create a posts.jsx file for the post page.

touch post.jsx

Moving forward, create a components folder inside the src folder; this is where our components will be stored. From the project’s structure, we will have six components with the following names: HomeHeader, Nav, PostLists, SinglePost, User and UserList.

Now, paste the following code inside HomeHeader.jsx:

import React from 'react'
import Styled from 'styled-components'
// icon
import { FaUsers } from 'react-icons/fa';
import { BsFilePost } from 'react-icons/bs';

const HomeHeader = ({title, post}) => {
    return (
        <StyledHeader>
            {
                post ?
                <BsFilePost className="icon" />:
                <FaUsers className="icon" />
            }
            {title}
        </StyledHeader>
    )
}
export default HomeHeader
const StyledHeader = Styled.div`
    width: 100%;
    padding: 2rem 1.5rem;
    font-size: 2.5rem;
    font-weight: 700;
    background: #170448;
    color: #fff;
    display: flex;
    align-items: center;
    justify-content: center;
    .icon {
        font-size: 4rem;
        color: #fff;
        margin-right: 1rem;
    }
`

The above code contains each page title we will be using later. We are just importing styled-components for the styling, react-icons for some icons and getting the title and post data as props.

Next, paste the following code into the Nav.jsx file:

import React from 'react'
import Styled from 'styled-components'
import { NavLink } from 'react-router-dom'
const Nav = () => {
    return (
        <StyledNav>
            <NavLink exact activeClassName="active" to="/">Home</NavLink>
            <NavLink exact activeClassName="active" to="/posts">Posts</NavLink>
        </StyledNav>
    )
}
export default Nav
const StyledNav = Styled.nav`
    width: 100%;
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    a {
        width: 100%;
        display: flex;
        align-items: center;
        justify-content: center;
        padding: 1rem;
        background: #fff;
        color: #170448;
        &.active, &:hover {
            background: #170448;
            color: #fff;
        }
    }
`

This component serves as a base to navigate the user from one page to another using NavLink from react-router-dom. We set the activeClassName to give the current page a different style and will stop here for now while continuing the remaining UI while fetching the data.

Let’s look at the standard way of routing in React versus using React Suspense. Here is the standard method of routing in React:

import {
  BrowserRouter as Router,
  Switch,
  Route
} from "react-router-dom";

pages
import Home from './pages/Home'
import Posts from "./pages/Posts";

function App() {
  return (
    <Router>
      <Nav />
      <Switch>
          <Route exact path="/">
            <Home />
          </Route>
          <Route exact path="/posts">
            <Posts />
          </Route>
      </Switch>
    </Router>
  );
}

So what is going on in the code block above? Using react-router-dom, we have to import BrowserRouter, Switch and Route.

  • BrowserRouter is used to wrap the routes; without it, the route won’t work.
  • Switch provides navigation between the routes; everything inside it is assigned to a route, while components outside show in all pages.
  • Route specifies the page to be rendered when a link is clicked.

Next, here’s routing using React Suspense:

import React, {lazy, Suspense} from "react";
import {
  BrowserRouter as Router,
  Switch,
  Route
} from "react-router-dom";
import Nav from "./components/Nav";
import { UserPlaceholder } from "./components/User";

const Home = lazy(() => import('./pages/Home'))
const Posts = lazy(() => import("./pages/Posts"))

function App() {
  return (
    <Router>
      <Nav />
      <Switch>
        <Suspense fallback={<UserPlaceholder />}>
          <Route exact path="/">
            <Home />
          </Route>
          <Route exact path="/posts">
            <Posts />
          </Route>
        </Suspense>
      </Switch>
    </Router>
  );
}
export default App;

What is the difference between these two approaches? Looking at the second approach, you will see that we are importing the pages with React Lazy. It allows us to use React Suspense with a fallback when the page is not loaded; This will enable us to download the data based on what the user is requesting.

React Suspense While Fetching Data

When trying to fetch data in React, there are three approaches to it:

  • Fetch on Render (without Suspense): This approach is the standard way of fetching data in a React app where you fetch the data when the component is rendered on the screen using componentDidMount or UseEffect. This approach introduces a flaw known as waterfall, whereby it has to wait for other components to render before it starts fetching, which may disrupt the flow of the program, especially if the data being fetched is important to the view.
// Using class component:
componentDidMount() {
  fetchItems();
}

// using function component:
useEffect(() => {
  fetchItems();
}, []);
  • Fetch Then Render (without Suspense): This is a way of fetching all the data before rendering. It solves the waterfall problem, but the user needs to wait for all the data to be fetched before interacting with the app. The UX may be frustrating, especially if the app fetches a lot of data that may slow down the app.
function fetchUsersData() {
  return Promise.all([
    getInfo(),
    getName()
  ]).then(([info,name]) => {
    return {info, name};
  })
}

Using Promise, we can fetch all the data and then use it one by one later when needed.

  • Render as You Fetch (using Suspense): This is the approach we will be talking about today, where we will be rendering while fetching. It starts rendering, and immediately the network request starts. Let’s look at how to set it up. Create an Api.js file inside the src folder and paste the following code inside.
const fetchUsers = () => {
    console.log('Fetching Users data....')
    return fetch('https://jsonplaceholder.typicode.com/users?_Limit=25')
    .then(response => response.json())
    .then(json => json)
}
const fetchPosts = () => {
    console.log('Fetching Users data....')
    return fetch('https://jsonplaceholder.typicode.com/posts?_limit=25')
    .then(response => response.json())
    .then(json => json)
}

Next, we will create a function for the data we want to fetch. The function typically uses Fetch API to get the data we need.

Then we can create our wrap Promise using this function and update the code with this. Add the code below to the top of the previous code.

const wrapPromise = (promise) => {
    // set initial status
    let status = 'pending'
    // store result
    let result
    // wait for promise
    let suspender = promise.then(
        res => {
            status = 'success'
            result = res
        },
        err => {
            status= 'error'
            result = err
        }
    )
    return {
        read() {
            if (status === 'pending') {
                throw suspender
            } else if (status === 'error') {
                throw result
            } else if (status === 'success') {
                return result
            }
        }
    }
}

The wrapPromise function takes a promise as a parameter; that is the function we created to fetch our data. Then we create a suspender variable where we check if the response returns success or error. We return an output based on the status.

Lastly, we create a fetchData function to collate all fetches using the code below.

export const fetchData = () => {
    const usersPromise = fetchUsers()
    const postPromise = fetchPosts()
    return {
        users: wrapPromise(usersPromise),
        posts: wrapPromise(postPromise)
    }
}

That is the function we will be exporting to use when trying to get our data.

Let’s create our remaining components to make the app functional. In the PostLists.js folder, paste this code:

import React from 'react'
import Styled from 'styled-components'
import { fetchData } from '../Api'
import {SinglePost} from './SinglePost'
const resource = fetchData()
const PostLists = () => {
    const posts = resource.posts.read()
    return (
        <StyledList>
            {
                posts.map(({ title, id, body }) => (
                    <SinglePost 
                        key={id}
                        title={title}
                        content={body}
                    />
                ))
            }
        </StyledList>
    )
}
export default PostLists
const StyledList = Styled.div`
    width: 100%;
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    grid-gap: .7rem;
`

Looking at the code above, you can see that we are using the fetchData that we just created, which is assigned to a resource variable; this is then used to get the post’s data using const posts = resource.posts.read().

We can now map through the post’s data and pass the data to the SinglePost component. To create the SinglePost component, open your SinglePost.js file and paste this code.

import React from 'react'
import Styled, {keyframes} from 'styled-components'

export const SinglePost = ({ title, content }) => {
    return (
        <StyledPost>
            <h3>{title}</h3>
            <p>{content}</p>
        </StyledPost>
    )
}
export const PostPlaceholder = () => {
    return (
        <StyledPost className="placeholder">
            <div className="title-placeholder"></div>
            <div className="content-placeholder"></div>
            <div className="content-placeholder"></div>
            <div className="content-placeholder"></div>
        </StyledPost>
    )
}
const Animate = keyframes`
    from {
        opacity: .4;
    }
    to {
        opacity: 1;
    }
`
const StyledPost = Styled.div`
    width: 100%;
    padding: 1rem;
    background: #fef7f7;
    &.placeholder {
        width: 100%;
        padding: 1rem;
        background: #d8cccc;
        animation: ${Animate} .6s ease-in-out infinite;
    }
    
    h3 {
        font-weight: 700;
        font-size: 1.5rem;
        color: #000;
    }
    .title-placeholder {
        width: 50%;
        height: 1.5rem;
        background: #fff;
        margin-bottom: 1rem;
    }
    p {
        font-size: 1rem;
        font-weight: 400;
        color: #000;
        line-height: 1.5;
        margin-top: .8rem;
    }
    .content-placeholder {
        width: 80%;
        height: 1rem;
        background: #fff;
        margin-bottom: .5rem;
    }
`

This component structure is straightforward; we have two sub-components inside. One is responsible for rendering each post while the other serves as the fallback—if the data is still fetching, that will be rendered.

Paste the following code inside the UserList.js file to show the list of users.

import React from 'react';
import {User} from './User';
import { fetchData } from '../Api'
const resource = fetchData()
const UserList = () => {
    const users = resource.users.read()
    return (
        <>
            {
                users.map(({ email, name, id, username}) => (
                    <User 
                        key={id}
                        email={email}
                        name={name}
                        imgUrl={`https://via.placeholder.com/32/${username}`}
                    />
                ))
            }
        </>
    )
}
export default UserList

We are using the same pattern as we did in the post list component: use the fetchData function to get the data and send the data to the User component by mapping through the data.

For the User component, paste the following code inside:

import React from 'react';
import Styled, {keyframes} from 'styled-components'

export const User = ({ imgUrl, name, email }) => {
    return (
        <StyledUser>
            <div className="user-details">
                <img src={imgUrl} alt={name} />
                <div className="user-name">{name}</div>
            </div>
            <div className="user-email">{email}</div>
        </StyledUser>
    )
}
export const UserPlaceholder = () => (
    <StyledUser className="placeholder">
        <div className="user-details">
            <div className="img-placeholder"></div>
            <div className="user-name placeholder" />
        </div>
        <div className="user-email placeholder" />
    </StyledUser>
)
const Animate = keyframes`
    from {
        opacity: .4;
    }
    to {
        opacity: 1;
    }
`
const StyledUser = Styled.div`
    width: 100%;
    padding: 1.5rem 1rem;
    margin: .8rem 0; 
    display: flex;
    align-items: center;
    justify-content: space-between;
    background: #fff8f8;
    border-radius: 8px;
    cursor: pointer;
    transition: all .3s ease-in-out;
    @media (max-width: 768px) {
        flex-direction: column;
        align-items: flex-start;
        justify-content: center;
    }
    &.placeholder {
        animation: ${Animate} .6s ease-in-out infinite;
    }
    &:hover {
        background: #f5ecec;
    }
    .user-details {
        display: flex;
        align-items: center;
        img {
            width: 32px;
            height: 32px;
            border-radius: 50%;
        }
        .img-placeholder {
            width: 32px;
            height: 32px;
            border-radius: 50%;
            background: #efdfdf;
        }
        .user-name {
            font-size: 1rem;
            font-weight: 500;
            color: #000;
            margin-left: 1rem;
            &.placeholder {
                width: 100px;
                height: 1.2rem;
                background: #efdfdf;
            }
        }
    }
    .user-email {
        font-size: 1rem;
        font-weight: 400;
        color: #000;
        @media (max-width: 768px) {
            margin-top: .8rem;
        }
        &.placeholder {
            width: 80px;
            height: 1.2rem;
            background: #efdfdf;
        }
    }
`

React Suspense in Component Level

Finally, let’s update the pages. Open the Home.js file and paste this code:

import React, { Suspense, SuspenseList } from 'react'
import HomeHeader from '../components/HomeHeader'
import UserList from '../components/UsersList'
import { UserPlaceholder } from '../components/User'
const Home = () => {
    return (
        <>
            <SuspenseList revealOrder="forwards">
                <Suspense fallback="loading....">
                    <HomeHeader title='Users' />
                </Suspense>
                <Suspense 
                    fallback={
                        <>
                            <UserPlaceholder />
                            <UserPlaceholder />
                            <UserPlaceholder />
                        </>
                    }>
                    <UserList />
                </Suspense>
            </SuspenseList>
        </>
    )
}
export default Home;

Looking at the code above, you can see that we are using SuspenseList and Suspense to render the files. SuspenseList is used to set the priority to which the data is fetched and rendered. In contrast, Suspense wraps the component and controls the state depending on if the data is still fetching or has completed the fetch.

Let’s work on the posting page. Paste the following code into the Posts.js file:

import React, { Suspense, SuspenseList } from 'react'
import Styled from 'styled-components'
import HomeHeader from '../components/HomeHeader'
import PostLists from '../components/PostLists'
import { PostPlaceholder } from '../components/SinglePost'
const Posts = () => {
    return (
        <>
            <SuspenseList revealOrder="forwards">
                <HomeHeader title="Posts" post />
                <Suspense 
                    fallback={
                        <Grid>
                            <PostPlaceholder />
                            <PostPlaceholder />
                            <PostPlaceholder />
                            <PostPlaceholder />
                            <PostPlaceholder />
                            <PostPlaceholder />
                        </Grid>
                    }
                >
                    <PostLists />
                </Suspense>
            </SuspenseList>
        </>
    )
}
export default Posts
const Grid = Styled.div`
    width: 100%;
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    grid-gap: .7rem;
`

Testing

Run the following command in the terminal to start up the project.

npm start

Project running in browser shows users list

Let’s check how effective it is, starting from the routing level. I was able to check the size before using React Suspense and after using it.

Here’s the size before using React Suspense—the bundle size was 8.1 kb.

Bundle file size without React Suspense - 8.1 kb

And after adding React Suspense, the bundle size decreased to 202 b.

Bundle file size with React Suspense - 202 b

Lastly, after using Suspend on both component and data fetching levels, I was able to get this:

lazy loading screen - first we see home and posts tabs at the top, then a posts header, then card indicators fading on to show loading

That improves the user’s visual perspective about the project, which may help them wait even longer even if the site is slow.

Conclusion

In this post, we covered what React, web performance, and code-splitting are. Also, we covered React Suspense and how to properly use it in routing level, component level and data-fetching level.


This content originally appeared on Telerik Blogs and was authored by Chinedu Imoh


Print Share Comment Cite Upload Translate Updates
APA

Chinedu Imoh | Sciencx (2022-01-28T15:45:00+00:00) Code-Splitting: Get a Better Performance When Developing With React. Retrieved from https://www.scien.cx/2022/01/28/code-splitting-get-a-better-performance-when-developing-with-react/

MLA
" » Code-Splitting: Get a Better Performance When Developing With React." Chinedu Imoh | Sciencx - Friday January 28, 2022, https://www.scien.cx/2022/01/28/code-splitting-get-a-better-performance-when-developing-with-react/
HARVARD
Chinedu Imoh | Sciencx Friday January 28, 2022 » Code-Splitting: Get a Better Performance When Developing With React., viewed ,<https://www.scien.cx/2022/01/28/code-splitting-get-a-better-performance-when-developing-with-react/>
VANCOUVER
Chinedu Imoh | Sciencx - » Code-Splitting: Get a Better Performance When Developing With React. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2022/01/28/code-splitting-get-a-better-performance-when-developing-with-react/
CHICAGO
" » Code-Splitting: Get a Better Performance When Developing With React." Chinedu Imoh | Sciencx - Accessed . https://www.scien.cx/2022/01/28/code-splitting-get-a-better-performance-when-developing-with-react/
IEEE
" » Code-Splitting: Get a Better Performance When Developing With React." Chinedu Imoh | Sciencx [Online]. Available: https://www.scien.cx/2022/01/28/code-splitting-get-a-better-performance-when-developing-with-react/. [Accessed: ]
rf:citation
» Code-Splitting: Get a Better Performance When Developing With React | Chinedu Imoh | Sciencx | https://www.scien.cx/2022/01/28/code-splitting-get-a-better-performance-when-developing-with-react/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.