6 quick optimization tips in React apps

6 quick optimization tips in React apps

In the last few years JavaScript frameworks have completely changed the way we build apps and React has had its fair share of the conversion process. Optimizing page load time is really important because the time a page takes to load directly correlates with bounce and conversion rates. In this blog post we will take a look at six common mistakes most developers make when building apps with React, we will also discuss how these mistakes can be avoided as well as highlight useful tips for keeping your page load time as low as possible.

TL:DR

Unnecessary imports Embedding functions in JSX Using the spread operator in DOM elements Bundle Compression Memoization Server Side Rendering

How React works

The image above explains what goes on each time you use a React app. In simple terms, every React application begins with a root component - which in our case here is App - and is composed of “children” components - AddGrocery, GroceryList and SearchBar. These children components consist of functions that render UI to the DOM based on properties and state contained in them.

Users interact with the rendered UI in different ways - by filling out a form or clicking a button. When this happens, events are relayed back to the parent component. These events cause a change in the state of the application thus prompting React to re-render the UI in its virtual DOM.

For every event that gets sent back up, React’s engine has to compare the virtual DOM with the real DOM and calculate if it is necessary to update the real DOM with this new found data. Now, where things get messy is if our App is structured in a way that every click and scroll results in React repeatedly comparing and updating changes between the virtual and the real DOM. This could result in a terribly slow and inefficient application.

Common Mistakes and how to avoid them

Below are three poor practices developers make when building applications. These mistakes decrease efficiency and increase page load times overall, avoid them as much as you can.

Unnecessary Imports

Whenever you import an entire library in your application, it is destructured to access the module you need. One or two small libraries may do no harm but when you are importing a lot of libraries and dependencies you should just import the module you need:


import assign from "101";
import Map from "immutable";

The code above imports the entire library and begins to destructure it to access assign and Map. Instead of doing that, let’s use a concept called “cherry picking” that only grabs the necessary parts of what your application needs:


import assign from "101/assign";
import Map from "immutable/src/map";

Embedding functions in JSX

JavaScript is well known as a garbage collected language. Its garbage collector manages memory by trying to reclaim memory no longer in use by parts of the application. Defining a function in render will create a new instance of the function each time the containing component is re-rendered. When this practice occurs all over your application it eventually becomes a problem for the garbage collector in the form of memory leaks - a situation where memory used by parts of the application is not released even though it is no longer in use by those parts:


class GroceryList extends React.Component {
    state = {
        groceries: [],
        selectedGroceryId: null
    }

    render(){
        const { groceries } = this.state;
        return (
           groceries.map((grocery)=>{
               return <Grocery onClick={(e)=>{
                    this.setState({selectedGroceryId:grocery.groceryId})
               }} grocery={grocery} key={grocery.id}/>
           }) 
        )
    }
}

The right thing would be to define a new function onGroceryClick right before render:


class GroceryList extends React.Component {
    state = {
        groceries: [],
        selectedGroceryId: null
    }

    onGroceryClick = (groceryId)=>{
        this.setState({selectedGroceryId:groceryId})
    }

    render(){
        const { groceries } = this.state;
        return (
           groceries.map((grocery)=>{
               return <Grocery onClick={this.onGroceryClick} 
                grocery={grocery} key={grocery.id}/>
           }) 
        )
    }
}

###

Using the spread operator in DOM elements

When building apps it is really poor practice to use the spread operator in an unreserved fashion. This will have unknown attributes flying around and it is only a matter of time before things begin to get really complicated. Here is a brief example:


const GroceriesLabel = props => {
    return (
      <div {...props}>
        {props.text}
      </div>
    );
  };

As the application you are building becomes larger, . . .props could contain anything. A better and safer practice is to identify whatever values you need:


const GroceriesLabel = props => {
    return (
      <div particularValue={props.particularValue}>
        {props.text}
      </div>
    );
};

Performance hacks

Use Gzip to compress your bundles

This is very efficient and can reduce the size of your files by as much as 65%. Most of the files in your application will use a lot of repeated text and whitespace. Gzip handles this by compressing these recurrent strings thus drastically shortening your website’s first render time. Gzip pre-compresses your files by using compressionPlugin - Webpack’s local plugin for compressing files during production, first let’s create a compressed bundle using compressionPlugin:



plugins: [
    new CompressionPlugin({
        asset: "[path].gz[query]",
        algorithm: "gzip",
        test: /.js$|.css$|.html$/,
        threshold: 10240,
        minRatio: 0.8
    })
]

Once the file has been compressed, it can then be served using middleware:


//this middleware serves all js files as gzip
app.use(function(req, res, next) {
    const originalPath = req.path;
    if (!originalPath.endsWith(".js")) {
        next();
        return;
    }
    try {
        const stats = fs.statSync(path.join("public", `${req.path}.gz`));
        res.append('Content-Encoding', 'gzip');
        res.setHeader('Vary', 'Accept-Encoding');
        res.setHeader('Cache-Control', 'public, max-age=512000');
        req.url = `${req.url}.gz`;

        const type = mime.lookup(path.join("public", originalPath));
        if (typeof type != 'undefined') {
            const charset = mime.charsets.lookup(type);
            res.setHeader('Content-Type', type + (charset ? '; charset=' + charset : ''));
        }
    } catch (e) {}
    next();
})

Memoizing React Components

The concept of memoization isn’t new - it is a technique that stores expensive function calls and returns the cached result when the same input occurs again. Memoization in React works by memoizing data computation so that changes in state happen as quickly as possible. To understand how this works, take a look at the React component below:


const GroceryDetails = ({grocery, onEdit}) => {
    const {name, price, grocery_img} = grocery;
    return (
        <div className="grocery-detail-wrapper">
            <img src={grocery_img} />
            <h3>{name}</h3>
            <p>{price}</p>
        </div>
    )
}

In the code sample above, all the children in GroceryDetails are based on props. Changing props will cause GroceryDetails to re-render. If GroceryDetails is a component that’s unlikely to change, it should be memoized. Users of earlier versions of React (< V16.6.0) would use moize, a memoization library for JavaScript. Take a look at the syntax below:


import moize from 'moize/flow-typed';

const GroceryDetails = ({grocery, onEdit}) =>{
    const {name, price, grocery_img} = grocery;

    return (
        <div className="grocery-detail-wrapper">
            <img src={grocery_img} />
            <h3>{name}</h3>
            <p>{price}</p>
        </div>
    )
}

export default moize(GroceryDetails,{
    isReact: true
});

In the code block above, moize stores any passed props and context in GroceryDetails and uses both of them to detect if there are updates to the component. As long as props and context remain unchanged, cached values will be returned. This ensures super quick rendering.

For users working with later versions of React (>= V16.6.0), you can use React.memo instead of moize:


const GroceryDetails = ({grocery, onEdit}) =>{
    const {name, price, grocery_img} = grocery;

    return (
        <div className="grocery-detail-wrapper">
            <img src={grocery_img} />
            <h3>{name}</h3>
            <p>{price}</p>
        </div>
    )
}

export default React.memo(GroceryDetails);

Consider using server side rendering

Server-Side Rendering — SSR from here on — is the ability of a front-end framework to render markup while running on a back-end system.

You should leverage SSR with Single Page Applications. Rather than have users wait for your JavaScript files to load, users of your application receive a fully rendered HTML page as soon as the initial request sent returns a response. Generally server-side rendered applications enable users receive content way faster than client-rendered applications. Some awesome solutions for server-side rendering in React include Next.js and Gatsby.

Summary

For the average beginner who may not be familiar with these concepts, I suggest you check out this course on getting started with React as it covers a ton - from handling state to creating components. The importance of optimising page load time in your React application cannot be underestimated. Not only will your app facilitate better SEO and command a higher conversion rate, by following best practices you will be able to detect errors more easily. These concepts may require a lot of efforts but they are worth giving a shot. Awesome posts such as Memoize React Components by Tony Quetano and How to serve a Webpack gzipped file in production by Selva Ganesh helped in writing this.

Like this article? Follow @fullstackmafia on Twitter