DEV Community

Brian Neville-O'Neill
Brian Neville-O'Neill

Posted on • Originally published at blog.logrocket.com on

7 optimization techniques in React

One key factor for positive user experience is speed i.e how much time a user has to wait to first see contents from your website or application. Amazon reports a 1% sales loss for every 100ms load time, Walmart reports a +2% conversion per one second load time improvement.

Negative statistics lead to a decrease in user satisfaction and eventually customers. One solution to mitigating this is to properly optimize your application.

In computer science, optimization is the selection of the best element (with regard to some criterion) from some set of available alternatives.

React allows us to build encapsulated components that manage their own state, then composes them to make complex UIs.

These components make up small parts of our UI. This means a lot of times we unintentionally create redundant components and structure our code in ways that can impact the overall loading time of our application.

As stated earlier, the solution is optimization, and in this case, we can describe it as the best way we can write a particular code block, function or component to achieve re-usability and reduce the time taken to return information mostly in a near instant manner.

With the help of some inbuilt APIs like React.Component, React.PureComponent and life-cycle methods React offers, we can optimize our components to ensure fast and efficient load times in our applications.

Typically, our components are composed of CSS, JS and HTML code. Being able to determine when certain pieces show up will have a great impact on your page speed.

In this tutorial, we will learn various optimization methods that use these inbuilt APIs, lifecycle methods and some other general techniques that ensure you improve your React code.

LogRocket Free Trial Banner

A basic understanding of React is assumed in this tutorial. You can learn more about React here.

How to use React.Fragment to avoid adding extra nodes to the DOM

At some point in your application, you will need to return multiple elements. From a table list to a group of related texts you will definitely reach scenarios where you need to return a group of data.

Your code will look something like this:

// Parent.js
class Parent extends React.Component {
    render() {
        return (
            <h1>Hello there!</h1>
            <h1>Hello there again!</h1>
        )
    }
}
Enter fullscreen mode Exit fullscreen mode

If you are using a linter you will see the error: JSX parent expressions must have one parent element you will be forced to wrap both elements in a parent element aka div like so:

<div>
  <h1>Hello there!</h1>
  <h1>Hello there again!</h1>
</div>
Enter fullscreen mode Exit fullscreen mode

Even though everything works fine, an extra unnecessary div is created. This can lead to so many useless elements being created around our application and can also cause invalid HTML in some cases where our render data is coming from a child component in a specific order. Consider the following code:

// Table.js
class Table extends React.Component {
  render() {
    return (
      <table>
        <tr>
          <Columns />
        </tr>
      </table>
    );
  }
}

class Columns extends React.Component {
  render() {
    return (
      <div>
        <td>column one</td>
        <td>column two</td>
      </div>
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

The above code will render the following in our table component:

<table>
  <tr>
    <div>
      <td>column one</td>
      <td>column two</td>
    </div>
  </tr>
</table>
Enter fullscreen mode Exit fullscreen mode

This is definitely not the intended output as it is an invalid HTML syntax. Fragment solves this for you. We can re-write our column component to:

// columns.js
class Columns extends React.Component {
  render() {
    return (
      <React.Fragment>
        <td>column one</td>
        <td>column two</td>
      </React.Fragment>
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

Now you get the intended output and even better no extra DOM node is created. This may seem small but in reality the more elements on a page the more time it will take to load. Therefore auditing pieces of your code and updating them to use fragments to group data where necessary will definitely improve your code and it’s performance. Find out more about fragments here.

Use React.Suspense and React.Lazy to load components declaratively

Typically, you want to load parts of your app only when they are requested. For instance, loading shopping cart data only when the cart icon is clicked, loading images at the bottom of a long image list when the user scrolls to that point, etc.

Lazy loading is a popular optimization technique widely used to speed up the load time of applications.

React.Lazy helps us load components on demand thereby reducing the load time of our application as only the needed pieces will be present as requested.

With its simple syntax, it can be used easily without hassle. It takes a callback function as a parameter that will load the component’s file using the dynamic import() syntax.

// MyComponent.js
class MyComponent extends Component{
    render() {
        return (<div>MyComponent</div>)
    }
}
const MyComponent = React.lazy(()=>import('./MyComponent.js'))
function App() {
    return (<div><MyComponent /></div>)
}
Enter fullscreen mode Exit fullscreen mode

Behind the scenes, at compile time a separate bundle is created by our webpack when it hits the React.lazy() and import() statement. This process is called Code-Splitting. Our final app will be separated into multiple bundles containing UI pieces that would be loaded whenever they are required.

Use React Suspense

In the time the component will be swapped in, a small time lag will occur leaving a screen freeze experience for your user. To give the user update or feedback on the outcome of the process React.Suspense is used.

React.Suspense is used to wrap lazy components to show fallback content while loading the component.

// MyComponent.js
const Mycomponent = React.lazy(()=>import('./component.js'))
function App() {
    return (
    <div>
        <Suspense fallback={<div>loading ..</div>}>
            <MyComponent />
        </Suspense>
    </div>)
}
Enter fullscreen mode Exit fullscreen mode

Now, whenever the component is being loaded and there’s a delay a fallback text loading. . will be rendered. Find out more about React.Suspense and .Lazy here.

Prevent unnecessary re-rendering with shouldComponentUpdate()

Most times in our application, we end up having instances of one component present on the screen. For example, on a blog page, we might have different blog posts show up via a blog post component that in turn also renders like button components. If not properly managed, a change in the state of a button component can cause all of the button components to re-render. The solution to this is using shouldComponentUpdate method.

shouldComponentUpdate() is used to let React know if a component’s output is not affected by the current change in state or props. By default, it re-renders on every state change. It always returns a boolean as a response — if the component should re-render or not. The default is that it always returns true.

A shouldComponentUpdate method is called with nextProps as the first argument and nextState as the second:

shouldComponentUpdate(nextProps, nextState){
    return nextProps.next !== this.props.next  
}
Enter fullscreen mode Exit fullscreen mode

Now, if the next prop hasn’t changed there’s no reason to change the appearance of the component by re-rendering. This may not seem like huge improvements, however, in an application with so many components re-rendering shouldComponentUpdate will help improve performance.

N/B: shouldComponentUpdate can cause unintended problems if you set it and forget , because your React component will not update normally. Consider using the built-in PureComponent instead of writing shouldComponentUpdate() by hand.

Use React.PureComponent

Instead of using shouldComponentUpdate method in our components, React introduced a new component with built-in shouldComponentUpdate implementation, the React.PureComponent component.

React.PureComponent is similar to React.Component. The difference between them is that React.Component doesn’t implement shouldComponentUpdate(), but React.PureComponent implements it with a shallow prop and state comparison.

No extra code is needed, all you need to do is to use it in your class declaration:

// use this
class MyComponent extends React.PureComponent{
    render() {
        return (<div>MyComponent</div>)
    }
}
// instead of
class MyComponent extends React.Component{
    render() {
        return (<div>MyComponent</div>)
    }
}
Enter fullscreen mode Exit fullscreen mode

Now with our pure component we no longer have to write:

shouldComponentUpdate(nextProps, nextState){
    return nextProps.next !== this.props.next  
}
Enter fullscreen mode Exit fullscreen mode

It already implements this by default for us.

Although this is the recommended way to use shouldComponentUpdate only extend PureComponent when you expect to have simple props and state, or use forceUpdate() when you know deep data structures have changed. Or, consider using immutable objects to facilitate fast comparisons of nested data. Find out more here.

Remove unused DOM elements with ComponentDidUnmount()

When working with React it is important to think of what happens when an element is removed from the DOM. Do they really go away? Or does the code just lie around even though it is not displayed to the user?

Having unused code around causes a problem called memory leak. React solves this for us by providing us with the componentWillUnmount method.

componentWillUnmount() is used to stop any unused code from running when a component is removed from the DOM.

You can perform several cleanups with this method, such as invalidating timers, canceling network requests, or cleaning up any subscriptions that were created in componentDidMount().

Consider the following component:

// App.js
class App extends Component {
    constructor(props) {
        super(props);
        this.state = {};
    }
    componentDidMount() {
        document.addEventListener("click", this.closeMenu);
    }
    openMenu = () => { }
    closeMenu = () => { }
    render() {
        return (
          <a href ="#" onClick = {this.closeMenu}>X</a>
        );
    }
}
Enter fullscreen mode Exit fullscreen mode

In the above code, when you click the X link without the componentDidUnmount() the menu is closed but the event listener which was created when the component was mounted is still available.

To fix that, we can add a componentDidUnmount() to our component:

componentWillUnmount() {
   document.removeEventListener("click", this.closeMenu);
}
Enter fullscreen mode Exit fullscreen mode

Now, when the button is clicked the event listener is removed with the componentDidUnmount() method.

N/B: You should not call setState() in componentWillUnmount() because the component will never be re-rendered. Once a component instance is unmounted, it will never be mounted again. Learn more about it here.

Use React.Memo to cache components

One way to speed up an application is by implementing memoization.

Memoization is an optimization technique used to primarily speed up programs by storing the results of expensive function calls and returning the cached results when the same inputs occur again.

A memoized function is faster because if the function is called with the same values as the previous one instead of executing function logic, it will instead fetch the result from cache.

In React, it is not uncommon for a component to change state multiple times. It is also not uncommon for some components to exist without the need to change state. If you have several components that rarely change state you should consider caching them.

React.Memo provides us with an easy API to implement memoization. It became available in React V16.6.0. Consider the following component:

// userDetails.js
const UserDetails = ({user}) =>{
    const {name, occupation} = user;
    return (
        <div>
            <h4>{name}</h4>
            <p>{occupation}</p>
        </div>
    )
}
Enter fullscreen mode Exit fullscreen mode

Currently, every time the userDetails function is called it executes the function over and over again even if these details rarely change. We can use React.memo to cache it:

export default React.memo(UserDetails)
Enter fullscreen mode Exit fullscreen mode

That’s all! As you can see no complex code is required. Simply wrap your component in the React.memo function and React takes care of the rest for you.

React.memo is a higher order component. It’s similar to React.PureComponent but for function components instead of classes. Find out more here.

Conclusion

In this tutorial, we explored several ways we can optimize our React components for better performance. We discussed only a few as there are lots of ways and tools used when optimizing an application.

Optimizing applications should be as needed because in some simple scenarios optimizing your components might be a killer.

One thing to keep in mind is the size and complexity of your project, would it be used for just demo or simple use cases? Or would it be deployed to be used every day by people? If the latter is the case then it may be time to think about optimizing your application. You can find out more about how React handles optimization internally here. Got questions or know other efficient ways to optimize applications? Let’s talk in the comments. Happy coding!


Plug: LogRocket, a DVR for web apps

 
LogRocket Dashboard Free Trial Banner
 
LogRocket is a frontend logging tool that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.
 
In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.
 
Try it for free.


The post 7 optimization techniques in React appeared first on LogRocket Blog.

Top comments (1)

Collapse
 
jivkojelev91 profile image
JivkoJelev91

This memo won't work properly.