React & Frontend

Guia Completo de React.memo em Profundidade: Quando Usar, Quando Evitar

12 min de leitura

Guia Completo de React.memo em Profundidade: Quando Usar, Quando Evitar

O Que é React.memo e Por Que Existe React.memo é um HOC (Higher Order Component) que otimiza componentes funcionais ao memorizar seu resultado. Quando um componente está envolvido com React.memo, ele só será renderizado novamente se suas props forem alteradas. Sem essa otimização, componentes são re-renderizados toda vez que o componente pai renderiza, mesmo que as props permaneçam idênticas. A razão pela qual React.memo existe está enraizada no ciclo de renderização do React. Diferentemente das classes com shouldComponentUpdate, componentes funcionais não têm um ciclo de vida nativo para controlar re-renderizações. React.memo preenche essa lacuna de forma elegante, mas como veremos, sua aplicação indiscriminada pode causar mais dano do que benefício. Entendendo o Mecanismo de Comparação Como React.memo Compara Props Por padrão, React.memo realiza uma comparação superficial (shallow comparison) das props. Isso significa que ele compara primitivos por valor e objetos/arrays por referência de memória. Se um objeto tem o mesmo conteúdo mas foi criado novamente, React.memo o considerará diferente. Neste

<h2>O Que é React.memo e Por Que Existe</h2>

<p>React.memo é um HOC (Higher Order Component) que otimiza componentes funcionais ao memorizar seu resultado. Quando um componente está envolvido com React.memo, ele só será renderizado novamente se suas props forem alteradas. Sem essa otimização, componentes são re-renderizados toda vez que o componente pai renderiza, mesmo que as props permaneçam idênticas.</p>

<p>A razão pela qual React.memo existe está enraizada no ciclo de renderização do React. Diferentemente das classes com shouldComponentUpdate, componentes funcionais não têm um ciclo de vida nativo para controlar re-renderizações. React.memo preenche essa lacuna de forma elegante, mas como veremos, sua aplicação indiscriminada pode causar mais dano do que benefício.</p>

<h2>Entendendo o Mecanismo de Comparação</h2>

<h3>Como React.memo Compara Props</h3>

<p>Por padrão, React.memo realiza uma comparação superficial (shallow comparison) das props. Isso significa que ele compara primitivos por valor e objetos/arrays por referência de memória. Se um objeto tem o mesmo conteúdo mas foi criado novamente, React.memo o considerará diferente.</p>

<pre><code class="language-javascript">const Usuario = React.memo(({ id, nome, endereco }) =&gt; {

console.log(&#039;Usuario renderizando:&#039;, nome);

return (

&lt;div&gt;

&lt;p&gt;ID: {id}&lt;/p&gt;

&lt;p&gt;Nome: {nome}&lt;/p&gt;

&lt;p&gt;Rua: {endereco.rua}&lt;/p&gt;

&lt;/div&gt;

);

});

export default function App() {

const [contador, setContador] = useState(0);

// Problema: endereco é criado a cada render

const endereco = { rua: &#039;Rua A&#039;, numero: 123 };

return (

&lt;div&gt;

&lt;p&gt;Contador: {contador}&lt;/p&gt;

&lt;button onClick={() =&gt; setContador(contador + 1)}&gt;

Incrementar

&lt;/button&gt;

&lt;Usuario

id={1}

nome=&quot;João&quot;

endereco={endereco}

/&gt;

&lt;/div&gt;

);

}</code></pre>

<p>Neste exemplo, o componente Usuario será renderizado a cada clique no botão, apesar das props reais (id, nome) não terem mudado. Por quê? O objeto <code>endereco</code> é recriado a cada render do componente pai, causando uma falha na comparação superficial.</p>

<h3>Função de Comparação Customizada</h3>

<p>Você pode fornecer uma função de comparação personalizada como segundo argumento para React.memo. Essa função recebe as props anteriores e as novas props, e deve retornar true se forem iguais (não renderizar) ou false se forem diferentes (renderizar).</p>

<pre><code class="language-javascript">const Usuario = React.memo(

({ id, nome, endereco }) =&gt; {

console.log(&#039;Usuario renderizando:&#039;, nome);

return (

&lt;div&gt;

&lt;p&gt;ID: {id}&lt;/p&gt;

&lt;p&gt;Nome: {nome}&lt;/p&gt;

&lt;p&gt;Rua: {endereco.rua}&lt;/p&gt;

&lt;/div&gt;

);

},

(prevProps, nextProps) =&gt; {

// Retorna true se props são iguais (NÃO renderiza)

// Retorna false se props são diferentes (renderiza)

return (

prevProps.id === nextProps.id &amp;&amp;

prevProps.nome === nextProps.nome &amp;&amp;

prevProps.endereco.rua === nextProps.endereco.rua

);

}

);

export default function App() {

const [contador, setContador] = useState(0);

const endereco = { rua: &#039;Rua A&#039;, numero: 123 };

return (

&lt;div&gt;

&lt;p&gt;Contador: {contador}&lt;/p&gt;

&lt;button onClick={() =&gt; setContador(contador + 1)}&gt;

Incrementar

&lt;/button&gt;

&lt;Usuario

id={1}

nome=&quot;João&quot;

endereco={endereco}

/&gt;

&lt;/div&gt;

);

}</code></pre>

<p>Agora o componente Usuario não será re-renderizado quando o contador mudar, pois a função de comparação verifica apenas as props que importam de verdade.</p>

<h2>Quando Usar React.memo Corretamente</h2>

<h3>Cenário 1: Listas Grandes com Itens Independentes</h3>

<p>React.memo brilha quando você tem listas grandes onde cada item é um componente independente. Sem memo, adicionar um item à lista faria todos os itens renderizarem novamente, mesmo os não-afetados.</p>

<pre><code class="language-javascript">const ListaItem = React.memo(({ id, texto, onDelete }) =&gt; {

console.log(&#039;ListaItem renderizando:&#039;, id);

return (

&lt;li&gt;

{id}: {texto}

&lt;button onClick={() =&gt; onDelete(id)}&gt;Deletar&lt;/button&gt;

&lt;/li&gt;

);

});

export default function TodoList() {

const [tarefas, setTarefas] = useState([

{ id: 1, texto: &#039;Estudar React&#039; },

{ id: 2, texto: &#039;Fazer exercícios&#039; },

{ id: 3, texto: &#039;Revisar conceitos&#039; },

]);

const handleDelete = useCallback((id) =&gt; {

setTarefas(tarefas.filter(t =&gt; t.id !== id));

}, [tarefas]);

return (

&lt;ul&gt;

{tarefas.map(tarefa =&gt; (

&lt;ListaItem

key={tarefa.id}

id={tarefa.id}

texto={tarefa.texto}

onDelete={handleDelete}

/&gt;

))}

&lt;/ul&gt;

);

}</code></pre>

<p>Note que usei useCallback para a função onDelete. Isso é crítico — sem ele, cada item seria re-renderizado porque onDelete seria uma nova função a cada render. Veremos isso em detalhes na próxima seção.</p>

<h3>Cenário 2: Componentes com Props Complexas Mas Raramente Alteradas</h3>

<p>Se um componente recebe muitas props mas elas mudam infrequentemente, React.memo reduz renders desnecessários.</p>

<pre><code class="language-javascript">const ConfiguracaoPainel = React.memo(({ usuario, tema, idioma, permissoes }) =&gt; {

console.log(&#039;ConfiguracaoPainel renderizando&#039;);

return (

&lt;div style={{ color: tema.cor }}&gt;

&lt;h2&gt;{usuario.nome}&lt;/h2&gt;

&lt;p&gt;Idioma: {idioma}&lt;/p&gt;

&lt;p&gt;Permissões: {permissoes.join(&#039;, &#039;)}&lt;/p&gt;

&lt;/div&gt;

);

});

export default function App() {

const [contador, setContador] = useState(0);

const usuario = useMemo(() =&gt; ({ nome: &#039;João&#039;, id: 1 }), []);

const tema = useMemo(() =&gt; ({ cor: &#039;azul&#039; }), []);

const idioma = &#039;português&#039;;

const permissoes = useMemo(() =&gt; [&#039;ler&#039;, &#039;escrever&#039;], []);

return (

&lt;div&gt;

&lt;p&gt;Render #{contador}&lt;/p&gt;

&lt;button onClick={() =&gt; setContador(contador + 1)}&gt;

Incrementar

&lt;/button&gt;

&lt;ConfiguracaoPainel

usuario={usuario}

tema={tema}

idioma={idioma}

permissoes={permissoes}

/&gt;

&lt;/div&gt;

);

}</code></pre>

<p>Aqui, useMemo garante que os objetos não sejam recriados, permitindo que React.memo funcione como esperado.</p>

<h2>O Lado Sombrio: Quando Evitar React.memo</h2>

<h3>O Problema da Validação Prematura de Otimização</h3>

<p>A maioria dos projetos usa React.memo incorretamente. O maior erro é aplicar React.memo indiscriminadamente, antes de medir o impacto real. Cada comparação adicional de props tem um custo — às vezes maior que uma simples re-renderização.</p>

<pre><code class="language-javascript">// ❌ Exemplo ERRADO: React.memo em um componente simples

const Botao = React.memo(({ label, onClick }) =&gt; {

return &lt;button onClick={onClick}&gt;{label}&lt;/button&gt;;

});

// Por quê está errado?

// 1. Comparar duas strings e uma função é tão rápido quanto renderizar o botão

// 2. Se onClick é criado inline, React.memo não ajuda mesmo assim</code></pre>

<p>Um componente tão simples não se beneficia de React.memo. O custo da comparação se iguala ao benefício da otimização.</p>

<h3>Quando Callback Props Causam Problemas</h3>

<p>React.memo só funciona se as props realmente não mudarem. Callbacks criados inline dentro do componente pai quebram completamente essa promessa.</p>

<pre><code class="language-javascript">// ❌ Problema clássico: callbacks inline

export default function ListaProdutos() {

const [filtro, setFiltro] = useState(&#039;&#039;);

const produtos = [&#039;Notebook&#039;, &#039;Mouse&#039;, &#039;Teclado&#039;];

return (

&lt;div&gt;

&lt;input

value={filtro}

onChange={(e) =&gt; setFiltro(e.target.value)}

/&gt;

{produtos.map(produto =&gt; (

&lt;ProdutoCard

key={produto}

nome={produto}

// ❌ Esta função é criada novamente a cada render!

onComprar={() =&gt; alert(Comprou: ${produto})}

/&gt;

))}

&lt;/div&gt;

);

}

const ProdutoCard = React.memo(({ nome, onComprar }) =&gt; {

console.log(&#039;ProdutoCard renderizando:&#039;, nome);

return (

&lt;div&gt;

&lt;p&gt;{nome}&lt;/p&gt;

&lt;button onClick={onComprar}&gt;Comprar&lt;/button&gt;

&lt;/div&gt;

);

});</code></pre>

<p>Neste código, React.memo falha completamente porque onComprar é uma nova função a cada render. A solução é useCallback:</p>

<pre><code class="language-javascript"></code></pre>

<h3>Quando Redux ou Context Dominam o Fluxo de Dados</h3>

<p>Se você usa Redux ou Context, React.memo pode ser contraproducente. Mudanças no store causam re-renderizações que React.memo não pode evitar.</p>

<pre><code class="language-javascript">// ❌ React.memo não ajuda aqui

const Contador = React.memo(({ valor }) =&gt; {

console.log(&#039;Contador renderizando&#039;);

return &lt;p&gt;Valor: {valor}&lt;/p&gt;;

});

export default function App() {

const valor = useSelector(state =&gt; state.contador.valor);

return (

&lt;div&gt;

{/* Toda vez que o Redux store muda, Contador renderiza

React.memo não consegue evitar isso */}

&lt;Contador valor={valor} /&gt;

&lt;/div&gt;

);

}</code></pre>

<p>Neste caso, o re-render é legítimo — o valor realmente mudou no store. React.memo apenas adiciona overhead de comparação sem benefício.</p>

<h2>Padrões Avançados e Armadilhas Comuns</h2>

<h3>Array e Objeto Props: O Vilão Silencioso</h3>

<p>Mesmo com React.memo, arrays e objetos nas props causam problemas se não forem memoizados no pai.</p>

<pre><code class="language-javascript"></code></pre>

<p>A lição: React.memo e useMemo frequentemente andam juntos. Se uma prop é um objeto ou array, ela deve estar envolvida em useMemo.</p>

<h3>Quando React.memo Encontra Children</h3>

<p>Props children sempre causam problemas com React.memo porque são funções JSX recriadas a cada render.</p>

<pre><code class="language-javascript">// ❌ Problema com children

const Card = React.memo(({ titulo, children }) =&gt; {

console.log(&#039;Card renderizando:&#039;, titulo);

return (

&lt;div&gt;

&lt;h2&gt;{titulo}&lt;/h2&gt;

{children}

&lt;/div&gt;

);

});

export default function App() {

const [contador, setContador] = useState(0);

return (

&lt;div&gt;

&lt;button onClick={() =&gt; setContador(contador + 1)}&gt;

Incrementar: {contador}

&lt;/button&gt;

{/ Children é recriado a cada render, quebrando React.memo /}

&lt;Card titulo=&quot;Meu Card&quot;&gt;

&lt;p&gt;Conteúdo: {contador}&lt;/p&gt;

&lt;/Card&gt;

&lt;/div&gt;

);

}</code></pre>

<p>Neste caso, React.memo não vai ajudar porque children muda toda vez que o componente pai renderiza. Isso é aceitável — o Card realmente precisa renderizar quando seu conteúdo mudar.</p>

<h2>Conclusão</h2>

<p>O React.memo não é um ganho automático de performance. É uma ferramenta específica para casos específicos: listas grandes onde itens são independentes, componentes com muitas props que raramente mudam, ou componentes custosos computacionalmente. Use React.memo apenas após identificar gargalos reais com ferramentas como React Profiler. Lembre-se que toda otimização prematura tem um custo em legibilidade e complexidade do código. Combine React.memo com useCallback e useMemo quando necessário — raramente você precisará de apenas um deles.</p>

<h2>Referências</h2>

<ul>

<li><a href="https://react.dev/reference/react/memo" target="_blank" rel="noopener noreferrer">React.memo - Documentação Oficial</a></li>

<li><a href="https://react.dev/reference/react/useCallback" target="_blank" rel="noopener noreferrer">useCallback - Documentação Oficial</a></li>

<li><a href="https://react.dev/reference/react/useMemo" target="_blank" rel="noopener noreferrer">useMemo - Documentação Oficial</a></li>

<li><a href="https://react.dev/learn/react-developer-tools#profiler" target="_blank" rel="noopener noreferrer">React Profiler - Ferramenta de Performance</a></li>

<li><a href="https://www.w3.org/webperf/" target="_blank" rel="noopener noreferrer">Web Performance Working Group - Best Practices</a></li>

</ul>

<p>&lt;!-- FIM --&gt;</p>

Comentários

Mais em React & Frontend

Boas Práticas de Zod com React Hook Form: Schemas Complexos e Erros Customizados para Times Ágeis
Boas Práticas de Zod com React Hook Form: Schemas Complexos e Erros Customizados para Times Ágeis

Introdução: Por Que Zod com React Hook Form? A validação de formulários é um...

Arquitetura de Frontend em Escala: Decisões, Trade-offs e Evolução: Do Básico ao Avançado
Arquitetura de Frontend em Escala: Decisões, Trade-offs e Evolução: Do Básico ao Avançado

Fundamentos de Arquitetura Frontend em Escala A arquitetura de frontend em es...

Guia Completo de Arquiteturas de Estado em React: Local, Global, Server e URL State
Guia Completo de Arquiteturas de Estado em React: Local, Global, Server e URL State

Introdução: Os Quatro Pilares do Gerenciamento de Estado O gerenciamento de e...