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 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 goes into the real DOM since there's nothing there yet. In turn, the virtual DOM remains stored within React for later updating
  3. The change in state causes the calculation of a new virtual DOM
  4. The difference between the old virtual DOM and the new one is calculated
  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 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 we often generate events, the virtual tree will become larger. So, the lag will increase.

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 allows optimization of the algorithm's performance by reducing the complexity to O(n).

The React checks the requirement for keys by itself. It will issue the 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. During typing, any click generates a whole virtual DOM from scratch. A good example is the Q&A on Hexlet, where we encountered this problem. The forum has a large virtual tree, and its full rendering takes a certain amount of time.

The React Developer Tools extension has a checkbox showing components rendered during events.

Everything is displayed. After each event, the rendered components become surrounded by frames.

If we observe an application without optimization, we will see that any event triggers rendering. But events tend to change only a part of the DOM. For example, entering text doesn't change the DOM in most cases.

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

Updating components triggers the following chain of functions:

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

You can stop recurrent rendering by using shouldComponentUpdate(). If this method returns false, the component will not render.

We assume that the component behaves as a pure function, so we can check inside this method that the props and 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'. We compare objects by reference. So, changing an object will show that it is the same, even though its contents have changed.

Since most components in typical applications behave like pure functions, and we store the state 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 is not that simple. It is easy to break PureComponent without realizing it.

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>
     );
  }
}

It is seemingly harmless code, but calling [] each time generates a new object, assuming options is false. It is easy to check: [] === [] will be false. If not, 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 the same. We generate a new handler function (callback) for each render function call, which makes updating ineffective. You already know the solution: define handlers as class-level properties.

Immutable.js library

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


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
profession
Development of front-end components for web applications
10 months
from scratch
Start at any time

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.