Before digging deeper into what can create performance problems in your React application, please take note of the following:

  • React is pretty fast by default, hence premature optimizations are not needed
  • React performance is different than your custom components performance
  • React's dev mode performance is much worse than React's build mode performance

Recent talks

Now there have been a lot of talks lately about how to make the VDOM or reconciliation faster.

TLDR - the discussions here are focusing on trying to find alternative methods to React's VDOM and reconciliation algos - those being an O(n) timespace complexity.

If you are not familiar with the Big O notation - here are a few hints:

O(1) - algo executes in the same time regardless of the input data set

const getFirstItem = (someArray) => Array.isArray(someArray) && someArray[0]

O(n) - algo performance depends linearly to its input data set (more data -> slower algo)

const toUpperCase = (someArray) => Array.isArray(someArray) && someArray.map(x => x.toUpperCase())

So getting back to React, for sure it's fast for a lot of things... but let's see where it doesn't actually shine.

Forms

Yep. Forms - let's check out some code that I intentionally made pretty unperformant. 👨‍💻

import React from "react";
import faker from "faker";

function Input({ onChange, value }) {
  console.log("Input - rendered");
  return <input type="text" onChange={onChange} value={value} />;
}

function DropdownItem() {
  console.log("DropdownItem - rendered");
  const option = faker.fake("{{hacker.verb}}");
  return <option value={option}>{option}</option>;
}

function Dropdown() {
  console.log("Dropdown - rendered");
  const [selected, setSelected] = React.useState("");

  return (
    <select
      value={selected}
      onChange={({ target }) => setSelected(target.value)}
    >
      {[...Array(1000)].map((_, i) => (
        <DropdownItem key={i} />
      ))}
    </select>
  );
}

function Form() {
  const [inputVal, setInputVal] = React.useState("");
  console.log("Form - rendered");

  return (
    <form>
      <h1> Form </h1>
      <Input
        value={inputVal}
        onChange={({ target }) => setInputVal(target.value)}
      />
      <Dropdown />
    </form>
  );
}

export default function App() {
  return (
    <div className="App">
      <Form />
    </div>
  );
}

We have the following components representation:
react components example

So, to render this - React created the following VDOM: VDOM1

As we can see, Input is a controlled component that changes the Form state every time the user types anything.

So when Input changes, React is creating a new VDOM tree starting at the component where something changed state or props -> the Form component: VDOM2

Now it will diff the previous VDOM (the green one) and it will see that the Input component needs to be changed in the real DOM. But also since each DropdownItem component is calling

faker.fake("{{hacker.verb}}")

to get a new option - each of the 1000 DropdownItems components are re-created in the DOM as well.

DropdownItem is an example of why you should try to keep components pure.

Inspect-View

Key takeaway ✨

All our components have been computed to create the new Virtual DOM - including all our 1000 DropdownItems 😱. So if our own components code is slow and we combine that with the fact that we are rendering a big amout of those components at a high frequency (every time the user types something), then we can bring the VDOM to its knees pretty easily.

Just to emphasize:

Every time a new VDOM is created, all your components code is executed

React, as all other technologies, has benefits and tradeoffs that we need to carefully consider.

Topics for the next time

This will be a 2 or 3 parts article - so stay tuned for next time when we will fix all the performance issues and discuss how to better engineer our components to avoid those problems in the first place.

If you like this article, chances are you will also enjoy what I post on Twitter. :fire:

More interesting reads:

This post is also available on DEV.