Register to get access to free programming courses with interactive exercises

Performance JS: React

Premature optimization is the root of all evil.

For starters, it's worth remembering that the virtual DOM is already an optimization that allows React to run out of the box fast enough that you don't have to think about performance at all for a long time. For many projects, this is enough for a lifetime.

In general terms, React works like this:

  1. Mounting causes the application to render.
  2. The resulting DOM is inserted into the real DOM in its entirety because there's nothing there yet. And the virtual DOM, in turn, is stored within React to be updated later.
  3. Changing the state results in a new virtual DOM being calculated.
  4. Calculates the difference between the old virtual DOM and the new one.
  5. The difference is applied to the real DOM.

React: reconciliation

Reconciliation

Every time there's a change to the state of a component, a mechanism called "reconciliation" is triggered, which calculates the difference (diff) between the past state and the new state. From an algorithmic point of view, it's essentially a search for differences in the two trees. Generally, the algorithm that performs this calculation works with a complexity of O(n3).

If events are generated often, and the virtual tree has become large, lag will start to get very visible.

To solve this problem, React insists you use a key attribute for all list items, which does not change for specific list items. This requirement makes it possible to optimize the performance of the algorithm by reducing the complexity to O(n).

The requirement for keys is checked by React itself. It will issue its own warnings in the browser console if it sees you're not using them.

Rendering

In practice, rendering the entire application (virtual DOM) for any change is expensive. Imagine the application uses a text input box. This means that during typing, any click generates a virtual DOM in its entirety, from scratch. A good example is the Q&A on Hexlet, where we encountered this very problem. The forum has a rather large virtual tree, and its full rendering takes a certain amount of time.

The React Developer Tools extension has a special checkbox that lets you see the components that are rendered during events. Everything is displayed visually, i.e., after each event, the rendered components are surrounded with frames.

It's easy to see that for an application where nothing has been specifically done, everything will be rendered for every event. But events tend to change only a small part of the DOM. Entering text often doesn't change the DOM at all.

React avoids rerendering the components that haven't changed. In terms of conditions – you have to keep it clean, in other words, the component must essentially be a clean function.

Updating components triggers the following chain of functions:

  1. getDerivedStateFromProps()
  2. shouldComponentUpdate()
  3. render()
  4. getSnapshotBeforeUpdate()
  5. componentDidUpdate()

You can stop rerendering by using shouldComponentUpdate(). If this method returns false, the component will not render at all. And since it's assumed that the component behaves as a pure function, it is sufficient to check inside this method that the props and state state haven't changed. It looks something like this:

shouldComponentUpdate(nextProps, nextState) {
  return !shallowEqual(this.props, nextProps)
    || !shallowEqual(this.state, nextState);
}

The shallowEqual() function compares only the top level of objects. Otherwise this operation would have been too heavy. By the way, here you can see why you can't change the state directly: this.state.mydata.key = 'value'. Since objects are compared by reference, changing an object will show that the object is the same, even though its contents have changed.

Since most components in typical applications do behave like pure functions, and the state is stored in a common root component, this technique can be applied everywhere, and React actively helps with this. So far you've only inherited classes from React.Component, but you can also inherit from React.PureComponent, in which shouldComponentUpdate has been correctly implemented for you.

See the Pen js_react_performance_pure_component by Hexlet (@hexlet) on CodePen.

If you click the button, you can see that the root component is re-rendered, but the subcomponent is not.

But it's not that simple. It's very easy to break PureComponent without realising..

Default Props

The first issue awaits if you don't work with the default properties correctly:

class Table extends React.Component {
  render() {
    const { options } = this.props;
    return (
      <div>
        {this.props.items.map(i =>
          <Cell data={i} options={options || []} />
         )}
       </div>
     );
  }
}

Seemingly harmless code, but calling [] each time generates a new object (assuming options is false). This is easy to check: [] === [] will be false. In other ways, the data have not changed, but <Cell> will be redrawn.

The takeaway is that you should use the built-in mechanism for default properties.

Callbacks

class App extends React.PureComponent {
  render() {
    return <MyInput
      onChange={e => this.props.update(e.target.value)} />;
  }
}

The problem in the code above is exactly the same: a new handler function (callback) is generated for each render function call, which breaks effective updating. You already know the solution: define handlers as class-level properties.

Immutable.js library

Another interesting way to solve the problem of re-rendering an application is to use persistent data structures, specifically the immutable.js library. This is a separate topic beyond the scope of this course.


Hexlet Experts

Are there any more questions? Ask them in the Discussion section.

The Hexlet support team or other students will answer you.

About Hexlet learning process

For full access to the course you need a professional subscription.

A professional subscription will give you full access to all Hexlet courses, projects and lifetime access to the theory of lessons learned. You can cancel your subscription at any time.

Get access
130
courses
1000
exercises
2000+
hours of theory
3200
tests

Sign up

Programming courses for beginners and experienced developers. Start training for free

  • 130 courses, 2000+ hours of theory
  • 1000 practical tasks in a browser
  • 360 000 students
By sending this form, you agree to our Personal Policy and Service Conditions

Our graduates work in companies:

<span class="translation_missing" title="translation missing: en.web.courses.lessons.registration.bookmate">Bookmate</span>
<span class="translation_missing" title="translation missing: en.web.courses.lessons.registration.healthsamurai">Healthsamurai</span>
<span class="translation_missing" title="translation missing: en.web.courses.lessons.registration.dualboot">Dualboot</span>
<span class="translation_missing" title="translation missing: en.web.courses.lessons.registration.abbyy">Abbyy</span>
Suggested learning programs

From a novice to a developer. Get a job or your money back!

Frontend Developer icon
Profession
beginner
Development of front-end components for web applications
start anytime 10 months

Use Hexlet to the fullest extent!

  • Ask questions about the lesson
  • Test your knowledge in quizzes
  • Practice in your browser
  • Track your progress

Sign up or sign in

By sending this form, you agree to our Personal Policy and Service Conditions
Toto Image

Ask questions if you want to discuss a theory or an exercise. Hexlet Support Team and experienced community members can help find answers and solve a problem.