Pular para o conteúdo

React Components Performance 101

React Components Performance 101

Hello my name is Luiz and i'm a software engineer from brazil and in this article i'll talk about some tips to improove your react applications performance. So without any further ado let's get started

Memos and Callbacks

So what is a memo on react, it's a way to reduce re-renders, for useMemo we use an dependencies approach and for React.memo uses a function that you can compare previous and actual props. Here goes an example that uses both approaches:

import * as React from "react";
import "./App.css";

import logo from "./logo.svg";

interface IItems {
  name: string;
  count: number;
}

interface IUpdaterProps {
  label: string;
  count: number;
  items: IItems[];
}

const Updater = React.memo<IUpdaterProps>(
  ({ count, label, items }) => {
    const title = React.useMemo(
      () => `${label}, Count = ${count}`,
      [count, label]
    );
    const total = React.useMemo(() => {
      console.log("Recalculate");
      return `Total: ${items.reduce((prev: number, cur: IItems) => {
        return prev + cur.count;
      }, 0)}`;
    }, [items]);

    return (
      <>
        <p className="App-intro">{title}</p>
        <p className="App-intro">{total}</p>
      </>
    );
  },
  (prevProps, nextProps) => {
    // They are equal?
    return (
      prevProps.count === nextProps.count &&
      prevProps.items.length === nextProps.items.length
    );
  }
);

const App: React.FC = () => {
  const [count, setCount] = React.useState(0);
  const [items, setItems] = React.useState<IItems[]>([]);
  const [inputState, setInputState] = React.useState("");

  const addItem = React.useCallback(
    (name: string) => {
      setItems((prev) => [
        ...prev,
        {
          count: Math.random() * 10,
          name,
        },
      ]);
    },
    [setItems]
  );

  const updateCount = React.useCallback(() => {
    setCount((prev) => prev + 1);
  }, [setCount]);

  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <h1 className="App-title">Welcome to React</h1>
        <input onChange={(e) => setInputState(e.target.value)} />
        <button onClick={() => addItem(inputState)}>Add Item</button>
      </header>

      <div onClick={updateCount}>
        <Updater items={items} label="Update Count" count={count} />
      </div>
    </div>
  );
};

export default App;

So if we look here the React.memo is a high order component that uses for the first argument a react component or functional component and also receives a callback function that you should return a boolean value to decide if the re-render will occur, and you have access to the previous and to the next props, so the approach is similar to the PureComponent that also uses a function for the lifecycle that runs before the render called shouldComponentUpdate and the return of both of them will decide if the re-render will occur or not.

So the React.memo it's an approach that is meant to be used with react components, but for memoizing values and functions we cannot use that method, but that's why we have the useMemo and the useCallback for helping us:

  • useMemo - The first argument that the use memo receives is a function that should only return a value (string, number, array, object, ts and etc...) and the second argument is a dependencies array that will be watched and if any of those dependencies change the value will be recalculated. If we don't use this method this can be very painful for the application to do some heavy math and also heavy loops because we will not be doing those recalculations at every re-render, but only when some value of the dependencies array changes
  • useCallback - The first argument that use callback receives is a normal function and a dependencies array that will rewrite the function that you wrote only when those values change. Because now with Functional Components the component is its own render method and every time that a re-render occurs if we are not using use callback to wrap our functions all of them will be rewritten unnecessarily.

So if we take a look at the example on the top and see the updater method we will see some use cases for it, an example, the total display value is a reduce that does math to display the total, so if that array of items gets too big will not be too much of an issue to the application because that total value will only be recalculated when the items array got updated

State Colocation

It's pretty common nowadays on React apps to have a global state like: Redux, Context API, Mobx and Etc...; and i've already saw some atrocities like using forms inside redux or even not using local state and using everything on the Global, and one thing that you should keep in mind is that the state should be closer to the component because if the state is higher than the component and the state is being used only on that component it doesn't make any sense to move it to a higher place like the global state. When we manage the state higher up in the React component tree, every update to that state results in an re-render of the entire React tree. React doesn't know what's changed, so it has to go and check all the components to determine whether they need DOM updates. That proccess can be very expensive when you have slower components on the React Tree. But if you move your state further down React has less to check and the execution will be way way faster.

Let's see an example!

function sleep(time) {
  const done = Date.now() + time;
  while (done > Date.now()) {
    // let's wait a bit...
  }
}
// imagine that this slow component is actually slow because it's rendering a lot of data (for example).
function SlowComponent({ time, onChange }) {
  sleep(time);
  return (
    <div>
      Wow, that was{" "}
      <input
        value={time}
        type="number"
        onChange={(e) => onChange(Number(e.target.value))}
      />
      ms slow
    </div>
  );
}
function UsernameForm({ time, username, onChange }) {
  return (
    <div>
      <label htmlFor="username">Username</label>
      <br />
      <input
        id="username"
        value={username}
        onChange={(e) => onChange(e.target.value)}
      />
      <p>
        {username
          ? `${username}'s favorite number is ${time}.`
          : "enter your username"}
      </p>
    </div>
  );
}
function App() {
  // our higher state
  const [username, setUsername] = React.useState("");
  const [time, setTime] = React.useState(200);
  return (
    <div>
      <UsernameForm time={time} username={username} onChange={setUsername} />
      <SlowComponent time={time} onChange={setTime} />
    </div>
  );
}

The problem here is that the username state is higher on the three and has the SlowComponent that sleeps for 200 ms, and it causes a slowdown when the user is typing. So in order to fix this we simply would move the username state up to its component as a local state and the performance issue will be solved!

function sleep(time) {
  const done = Date.now() + time;
  while (done > Date.now()) {
    // let's wait a bit...
  }
}
// imagine that this slow component is actually slow because it's rendering a lot of data (for example).
function SlowComponent({ time, onChange }) {
  sleep(time);
  return (
    <div>
      Wow, that was{" "}
      <input
        value={time}
        type="number"
        onChange={(e) => onChange(Number(e.target.value))}
      />
      ms slow
    </div>
  );
}
function UsernameForm({ time }) {
  const [username, setUsername] = React.useState("");
  return (
    <div>
      <label htmlFor="username">Username</label>
      <br />
      <input
        id="username"
        value={username}
        onChange={(e) => setUsername(e.target.value)}
      />
      <p>
        {username
          ? `${username}'s favorite number is ${time}.`
          : "enter your username"}
      </p>
    </div>
  );
}
function App() {
  // our higher state
  const [time, setTime] = React.useState(200);
  return (
    <div>
      <UsernameForm time={time} />
      <SlowComponent time={time} onChange={setTime} />
    </div>
  );
}

Debounce

Debouncing is a technique to prevent the event trigger from being fired too often, so if you are typing on a search input and the api request is being made at every type debounce will solve that for us so what is it going todo? It will avoid the api call while the user is typing and when he ends typing the request will be made! Let's see an example

import { useState, useEffect, useRef } from "react";

type Result = {
  title: string;
  description;
};

export default function SearchPage() {
  const [text, setText] = useState("");
  const [results, setResults] = useState<Result[]>();

  useEffect(() => {
    const results = await getResults(text);
    setResults(results);
  }, [text]);

  return (
    <>
      <input name="search" value={text} onChange={e.target.value} />
      {results.map(({ title, description }) => (
        <div>
          <p>{title}</p>
          <span>{description}</span>
        </div>
      ))}
    </>
  );
}

So what will happen here every time that we type on the search input a request will be made and this can be very bad because you will be making a lot of requests to the backend and you will be using only one. So that's is where debounce solves the issue. Lets see how to fix it.

import { useState, useEffect } from "react";
import { useDebounce } from "use-debounce";

type Result = {
  title: string;
  description;
};

export default function SearchPage() {
  const [text, setText] = useState("");
  const [results, setResults] = useState<Result[]>();
  const [value] = useDebounce(
    // the value that is being debounced
    text,
    // the delay in ms to the search happen
    200
  );

  useEffect(() => {
    const results = await getResults(value);
    setResults(results);
  }, [value]);

  return (
    <>
      <input name="search" value={text} onChange={e.target.value} />
      {results.map(({ title, description }) => (
        <div>
          <p>{title}</p>
          <span>{description}</span>
        </div>
      ))}
    </>
  );
}

So to fix this i added a library called use-debounce that you simply pass the value that you want to debounce and the delay and after, and when you finish typing on the search input he will wait 200ms to see if something else changes if it not changes it stores the last update into the value variable and when the value variable chages our useEffect will hit the api and search for the results.

Thanks!

If you liked this article please clap and share on your social networks to reach more people and help them!

Profile picture
Luiz Fernando - Senior Software Engineer

Obrigado por ler!

Espero que você tenha gostado de ler este artigo. Se você tiver alguma dúvida ou feedback, não hesite em entrar em contato comigo nas minhas redes sociais abaixo. Tenha um ótimo dia!

Carousel imageCarousel imageCarousel imageCarousel imageCarousel image
Próximo / Tudo começa com uma boa conversa

Grandes ideias.
Little Luiz.

Um produto para tirar do papel? Um time para fortalecer? Vamos descobrir o que podemos construir juntos.

Escolha seu caminho
01Freelance / Products

Tenho um projeto

Freelances, produtos e desafios técnicos para resolver juntos.

  • Da descoberta do produto à entrega
  • Engenharia web, mobile e backend
  • Escopo claro. Colaboração direta.
Vamos criar algo
02Hiring / Teams

Estou formando um time

Vagas de engenharia e oportunidades para construir a longo prazo.

  • Engenharia full-stack sênior
  • Visão de produto e responsabilidade técnica
  • Experiência com times distribuídos
Me chame para o seu time
03Résumé / Versions

Explore currículos

Versões sob medida para engenharia geral, health-tech, fintech e mais.

  • Seis variantes focadas
  • Exportação PDF pronta para impressão
  • Atualizado para cada vaga
Veja meu currículo
luizepauloxd@gmail.com

Prefere e-mail? Escreva diretamente ou comece um rascunho aqui.

Me conte o que você tem em mente.

© 2026 Luiz Fernando Feito com intenção. E um pouco de curiosidade.Voltar ao topo