Use the New Profiler in React Developer Tools to Generate Flame Charts and Interactions
September 10, 2018
React version 16.5.0 has been released and one of the features that it supports is the new Profiler in the React Developer Tools. In the above video and following blog post, we will update an existing app to ⚛ React 16.5.0 and show off various features of the new Profiler. The tool will auto-record a lot of information from your React application, but you can also add interaction tracking to gain insight into user-generated events.
Installation
Before you can utilize the new Profiler feature of the React Developer Tools, you'll need two things...
- React v16.5.0+ (Developer Build)
- React Developer Tools v3.3.2+
NOTE: If you want to profile in production see the Profiling in Production gist from Brian Vaughn (@brian_d_vaughn).
Profiling Your Application
For this blog post, I'm using a copy of the Redux TodoMVC Example app from the redux repository. You can find the code for this video/post at github.com/elijahmanor/redux
Empty Profiler Tab
Initially, the Profiler tab will look empty (see the following screenshot). To assess the performance of your application, you'll need to gather profiling data first. You can do this by clicking the ⏺ record button and then start interacting with portions of your application that you want to profile. Once you're done interacting with your app you can click the "Stop" button.
Initial Flamegraph after Gathered Data
Once you've gathered profile data, you'll immediately see Flamegraph data for the first render that occurred (see the following screenshot). For each render, you can see what components were involved and which ones took the most time to complete. The gray bars represent components that didn’t need to re-render at all (they could have been PureComponents
or have implemented shouldComponentUpdate
manually).
Single-Click to View a Component's Details
If you single-click on one of the components (like TodoTextInput
for example) then the Flamegraph will zoom into that particular component tree (see the following screenshot) and the right panel will give more detail about that component including its props
and state
.
Double-Click to View a Comparison Across All Renders
You can double-click on an item (like MainSection
for example) and see a comparison graph of how long it took across all renders. And then you can drill into one of the instances by double-clicking on it to see which render it was related to.
Ranked Flat View of Components
You can also look at a ranked flat view of the components from top to bottom. The items at the top took the longest and the items at the bottom took the least amount of time to render. This view can help when trying to make sense of all the data presented.
Tracking User Interactions
There is another view called "Interactions", but it’ll probably be empty by default. You have to add some code to your app before anything will show up here.
The point of interactions is to give meaningful names to user events. The idea is to associate a render in the Profiler based on the intent of what happened. For example, it'd be nice if we could see the render when a Todo item is being added or when one is being completed.
There's a nice gist describing the ins and outs of Interaction Tracking with React written by Brian Vaughn (@brian_d_vaughn).
Installation
To begin, you'll need to install the schedule
package that contains a track
function that we'll need to invoke manually when tracking an interaction.
npm install schedule
NOTE: The
schedule
API is unstable at the moment and could change slightly going forward.
Manual Tracking
Using the unstable_track
function from schedule/tracking
, we can manually
add calls around code that will initiate a render or re-render. For our Redux
TodoMVC app, let's update the TodoTextInput.js
file and tweak the
handleSubmit
method. We'll take our onSave
and setState
code and provide it as a callback to the track
function.
/components/TodoTextInput.js
/* ... more code ... */
import { unstable_track as track } from 'schedule/tracking'
export default class TodoTextInput extends Component {
/* ... more code ... */
handleSubmit = (e) => {
const text = e.target.value.trim()
if (e.which === 13) {
track('Add Todo', performance.now(), () => {
this.props.onSave(text)
if (this.props.newTodo) {
this.setState({ text: '' })
}
})
}
}
/* ... more code ... */
}
At this point, if we re-record Profile data from our app we'll notice an entry in the Interactions tab. There is our "Add Todo" interaction! You can click on the entry for more details on the right panel, and you can also click to go straight to the associated render.
Automatic Tracking of Redux Actions
It could be quite cumbersome to manually add a bunch of track
calls throughout
your codebase. However, since this is a Redux app, let's create a function
instead that iterates over the actions
and wraps them in a track
call
automatically!
/containers/MainSection.js
/* ... more code ... */
import { unstable_track as track } from 'schedule/tracking'
/* ... more code ... */
const trackActions = (object) =>
Object.keys(object).reduce((memo, name) => {
const action = object[name]
memo[name] = (...args) =>
track(name, performance.now(), () => action(...args))
return memo
}, {})
const mapDispatchToProps = (dispatch) => ({
actions: trackActions(bindActionCreators(TodoActions, dispatch)),
})
/* ... more code ... */
As before, we'll need to re-record to gather new Profiler data. Once the data is
retrieved, we should see more interactions to investigate. In the following
screenshot, you can see the original "Add Todo" track event and also two new
track events that were a result of our Redux actions (completeAllTodos
and clearCompleted
).
Conclusion
The ability to profile a React application is very powerful and I’m super excited about having these advanced features in the React Developer Tools. Thank you React team!
Tweet about this post and have it show up here!