Integrating React Profiler

February 21st, 2019. Tagged: react

Say hello to the new perf hotness - a profiler built into React. This announcement blog post shows how to use it as a Chrome Extension, but you can also use it in your code, to get insight into real world interactions, even in production. Let's see how.

Create a new react app

If you're not familiar, there's a create-react-app tool that gets you started quickly with a new app.

npm i -g create-react-app
npx create-react-app my-app
cd my-app
npm start

Boom! A new app is born, showing a rotating logo.

Stop spinning

Now let's make that logo start/stop spinning on click. Tweak the CSS first.

Before:

.App-logo {
  animation: App-logo-spin infinite 20s linear;
  height: 40vmin;
  pointer-events: none;
}

After:

.App-logo-spin {
  animation: App-logo-spin infinite 20s linear;
}

Then handle the click:

Before:

<img src={logo} className="App-logo" alt="logo" />

After:

<img
  src={logo}
  className={this.state.spin ? 'App-logo App-logo-spin' : 'App-logo'}
  alt="logo"
  onClick={() => this.setState({spin: !this.state.spin})}
/>

... which also requires a spin state:

constructor(props) {
  super(props);
  this.state = {
    spin: true,
  };
}

Now clicking on the logo toggles the spinning.

Profile

Profiling this interaction (and any other in your app) is simply a question of wrapping the interesting part (even your whole app) in a Profiler component. Let's wrap everything, which means something like:

// require
import React, {Component, unstable_Profiler as Profiler} from 'react';

And then wrap:

<Profiler id="profi" onRender={onRender}>
  <div className="App">etc.</div>
</Profiler>

So you give the profiler an ID since you can have multiple interesting parts of the app profiled, and an onRender callback.

Now this callback is invoked every time ReactDOM does a "commit" to the actual DOM. Which is usually the expensive part, and something you want to do as fewer times as practical. And what goes into the callback? Here's an example:

function onRender(id, phase, actualDuration, baseDuration, startTime, commitTime) {
  if (id !== 'profi') { // optional
    return;
  }
  console.log(arguments);
}

So now what happens when you load the app and click the logo twice? You see the initial rendering (phase: mount) and the two updates (phase: update)

["profi", "mount", 5.500000013853423, 1.0450000263517722, 10696.320000002743, 10703.885000009905]
["profi", "update", 0.9149999968940392, 0.37500001781154424, 21110.35499999707, 21111.57500000263]
["profi", "update", 0.37000000884290785, 0.14500002725981176, 24351.725000000442, 24352.49499999918]

What do these numbers mean? The first two are durations (actual and base) explained here. The other two are when the commit is starting when it's done, explained here.

And finally: the profiling React build, see here. Now in addition to "dev" and "prod" you have "profiling" which is prod+profiling, in other words a fast prod version without all the dev overhead.

And that's about it - time to get profiling!

Comments? Feedback? Find me on Twitter, Mastodon, Bluesky, LinkedIn, Threads