Categories
JavaScript React

Profile React App Performance with the Profile Component

Spread the love

React is a library for creating front end views. It has a big ecosystem of libraries that work with it. Also, we can use it to enhance existing apps.

In this article, we’ll look at how to profile a React app’s performance with the Profiler API.

Profiler API

The Profiler component measures how often a React app renders and what the cost of rendering is.

It helps identify parts of an app that are slow and may benefit from optimizations like memoization.

This component adds overhead, so it’s disabled in the production build.

Basic Usage

We can use it to measure performance as follows:

import React, { Profiler } from "react";  
import ReactDOM from "react-dom";  
import { unstable_trace as trace } from "scheduler/tracing";

class App extends React.Component {  
  constructor(props) {  
    super(props);  
    this.state = { msg: "" };  
  } 

  onRender(  
    id,  
    phase,  
    actualDuration,  
    baseDuration,  
    startTime,  
    commitTime,  
    interactions  
  ) {  
    const performanceData = [  
      `id: ${id}`,  
      `phase: ${phase}`,  
      `actualDuration: ${actualDuration}`,  
      `baseDuration: ${baseDuration}`,  
      `startTime: ${startTime}`,  
      `commitTime: ${commitTime}`,  
      `interactions: ${JSON.stringify([...interactions])}`  
    ].join("\n");  
    console.log(performanceData);  
  } 

  componentDidMount() {  
    trace("initial render", performance.now(), () => {  
      this.setState({ msg: "foo" });  
    });  
  } 

  render() {  
    return (  
      <Profiler id="app" onRender={this.onRender.bind(this)}>  
        <p>{this.state.msg}</p>  
      </Profiler>  
    );  
  }  
}

const rootElement = document.getElementById("root");  
ReactDOM.render(<App />, rootElement);

In the code above, we have the Profiler component wrapped around our main app code in the rendr method.

We add an id prop so that we can identify what part of the React tree was committed if we’re using multiple profilers.

Also, we have an onRender prop which is set the our onRender method that we defined.

onRender is a callback that has the following parameters:

  • id — a string that we set to identify the part of the React component tree that we just committed.
  • phase — a string that can either have the value 'mout' or 'update' . This identifies whether the tree has just been mounted for the first time or re-rendered due to a change in props, state or hooks.
  • actualDuration — a number that shows the time spent rendering the Profiler and its descendants for the current update. This indicates how well the subtree makes use of memoization (e.g. with React.memo , useMemo , shouldComponentUpdate ). This should decrease significant after the initial mount since child components will only be re-rendered if props change
  • baseDuration — a number that indicates the duration of the most recent render time of each individual component within the Profiler tree. This estimates a worst-case cost of rendering
  • startTime — a timestamp when React began rendering the current update
  • commitTime — a timestamp when React committed to the current update. It’s shared between all profilers in a commit, enabling them to be grouped
  • interactions — a Set of interactions that were being traced when render or setState were called.

In the onRender method, we logged all the parameter values, and we get the following when App renders:

id: app  
phase: update  
actualDuration: 0.38499990478157997  
baseDuration: 0.045000109821558  
startTime: 908.5849998518825  
commitTime: 909.2250000685453  
interactions: [{"_count":1,"id":0,"name":"initial render","timestamp":906.7250001244247}]

The interactions output is from the trace method call. It’s tracking the time when the code inside the callback we passed as the last argument of trace we run.

This lets us associate the performance information with the events that caused the app to render.

From the output above, we can see that the render was caused by an update, and it’s because of the initial render interaction that we have in the componentDidMount lifecycle hook.

In the trace function call, we have the string with the name that we set to identify the interaction, performance.now() is a more precise version of Date.now() to get the current date-time which we pass in as the timestamp to start the trace.

The 3rd argument is a callback with the code that we want to trace the interaction for.

Conclusion

We can use the Profiler component to add performance tracking capabilities when we’re developing the app.

It’s useful because we can use it to see what parts of the React app is performing slowly and can benefit from caching or memoization.

To tracking interactions, i.e., the events that caused an update to be scheduled, we can use the unstable_trace function from the scheduler/tracing package to trace the timing of the interactions.

By John Au-Yeung

Web developer specializing in React, Vue, and front end development.

Leave a Reply

Your email address will not be published. Required fields are marked *