React & Frontend

Boas Práticas de Batching Automático em React 18: Atualizações Síncronas e Assíncronas para Times Ágeis

11 min de leitura

Boas Práticas de Batching Automático em React 18: Atualizações Síncronas e Assíncronas para Times Ágeis

O que é Batching Automático no React 18? Batching é o processo pelo qual o React agrupa múltiplas atualizações de estado em uma única renderização. Antes do React 18, esse comportamento era inconsistente: funcionava perfeitamente dentro de event handlers, mas falhava em Promises, setTimeout ou callbacks de bibliotecas externas. O React 18 introduziu o Automatic Batching, que aplica essa otimização de forma consistente em praticamente todos os cenários. Essa mudança reduz significativamente o número de renderizações desnecessárias, melhorando a performance de aplicações que lidam com múltiplas atualizações de estado simultâneas. Para entender por que isso importa, imagine um formulário onde você precisa atualizar cinco campos diferentes — sem batching, React renderizaria cinco vezes; com batching, renderiza uma única vez. Entendendo o Batching Automático em Cenários Síncronos Event Handlers e Atualizações Imediatas No React 18, quando você dispara múltiplas atualizações de estado dentro de um event handler (como um clique de botão), elas são automaticamente agrupadas em um único ciclo de

<h2>O que é Batching Automático no React 18?</h2>

<p>Batching é o processo pelo qual o React agrupa múltiplas atualizações de estado em uma única renderização. Antes do React 18, esse comportamento era inconsistente: funcionava perfeitamente dentro de event handlers, mas falhava em Promises, setTimeout ou callbacks de bibliotecas externas. O React 18 introduziu o <strong>Automatic Batching</strong>, que aplica essa otimização de forma consistente em praticamente todos os cenários.</p>

<p>Essa mudança reduz significativamente o número de renderizações desnecessárias, melhorando a performance de aplicações que lidam com múltiplas atualizações de estado simultâneas. Para entender por que isso importa, imagine um formulário onde você precisa atualizar cinco campos diferentes — sem batching, React renderizaria cinco vezes; com batching, renderiza uma única vez.</p>

<h2>Entendendo o Batching Automático em Cenários Síncronos</h2>

<h3>Event Handlers e Atualizações Imediatas</h3>

<p>No React 18, quando você dispara múltiplas atualizações de estado dentro de um event handler (como um clique de botão), elas são automaticamente agrupadas em um único ciclo de renderização. Isso era verdade também no React 17, mas é importante revisar esse comportamento como base.</p>

<pre><code class="language-jsx">import React, { useState } from &#039;react&#039;;

export function SyncBatchingExample() {

const [count, setCount] = useState(0);

const [isLoading, setIsLoading] = useState(false);

const [message, setMessage] = useState(&#039;&#039;);

console.log(&#039;Renderização&#039;);

const handleClick = () =&gt; {

// Todas essas três atualizações serão agrupadas em uma ÚNICA renderização

setCount(prev =&gt; prev + 1);

setIsLoading(true);

setMessage(&#039;Atualizando...&#039;);

};

return (

&lt;div&gt;

&lt;p&gt;Count: {count}&lt;/p&gt;

&lt;p&gt;Loading: {isLoading ? &#039;Sim&#039; : &#039;Não&#039;}&lt;/p&gt;

&lt;p&gt;Message: {message}&lt;/p&gt;

&lt;button onClick={handleClick}&gt;Atualizar Estado&lt;/button&gt;

&lt;/div&gt;

);

}</code></pre>

<p>Quando você clica no botão acima, verá &quot;Renderização&quot; impressa apenas uma vez no console, não três. As três chamadas <code>setState</code> são agrupadas automaticamente. Esse comportamento também ocorre em outros contextos síncronos como callbacks diretos e manipulações imediatas de estado.</p>

<h2>Batching Automático em Cenários Assíncronos</h2>

<h3>Promises e Callbacks Assíncronos</h3>

<p>Essa é a grande inovação do React 18. Anteriormente, atualizações dentro de Promises, setTimeout ou callbacks de bibliotecas externas <strong>não eram batched</strong>. Agora, elas são automaticamente agrupadas também.</p>

<pre><code class="language-jsx">import React, { useState } from &#039;react&#039;;

export function AsyncBatchingExample() {

const [count, setCount] = useState(0);

const [isLoading, setIsLoading] = useState(false);

const [data, setData] = useState(null);

console.log(&#039;Renderização&#039;);

const handleAsyncUpdate = async () =&gt; {

// Iniciar carregamento

setIsLoading(true);

try {

// Simular chamada à API

const response = await fetch(&#039;https://jsonplaceholder.typicode.com/posts/1&#039;);

const json = await response.json();

// No React 18, essas duas atualizações serão agrupadas

// mesmo dentro de uma Promise

setIsLoading(false);

setData(json);

setCount(prev =&gt; prev + 1);

} catch (error) {

setIsLoading(false);

setData(null);

}

};

return (

&lt;div&gt;

&lt;p&gt;Count: {count}&lt;/p&gt;

&lt;p&gt;Loading: {isLoading ? &#039;Carregando...&#039; : &#039;Concluído&#039;}&lt;/p&gt;

{data &amp;&amp; &lt;p&gt;Data Title: {data.title}&lt;/p&gt;}

&lt;button onClick={handleAsyncUpdate}&gt;Carregar Dados&lt;/button&gt;

&lt;/div&gt;

);

}</code></pre>

<p>Quando você clica em &quot;Carregar Dados&quot;, a chamada à API é iniciada. Após a resposta, as três atualizações de estado (setIsLoading, setData e setCount) são agrupadas em uma única renderização. No React 17, isso causaria renderizações separadas, reduzindo a performance em aplicações data-heavy.</p>

<h3>setTimeout e Callbacks de Bibliotecas Externas</h3>

<p>O batching automático também funciona com setTimeout e outros padrões assíncronos que eram problemáticos anteriormente.</p>

<pre><code class="language-jsx">import React, { useState } from &#039;react&#039;;

export function TimeoutBatchingExample() {

const [firstValue, setFirstValue] = useState(&#039;&#039;);

const [secondValue, setSecondValue] = useState(&#039;&#039;);

const [thirdValue, setThirdValue] = useState(&#039;&#039;);

console.log(&#039;Renderização&#039;);

const handleComplexUpdate = () =&gt; {

// Simular múltiplas operações assíncronas com setTimeout

setTimeout(() =&gt; {

// React 18 agrupa essas três atualizações em uma renderização

setFirstValue(&#039;Valor 1&#039;);

setSecondValue(&#039;Valor 2&#039;);

setThirdValue(&#039;Valor 3&#039;);

}, 1000);

};

return (

&lt;div&gt;

&lt;p&gt;1: {firstValue}&lt;/p&gt;

&lt;p&gt;2: {secondValue}&lt;/p&gt;

&lt;p&gt;3: {thirdValue}&lt;/p&gt;

&lt;button onClick={handleComplexUpdate}&gt;

Iniciar Atualização com Delay

&lt;/button&gt;

&lt;/div&gt;

);

}</code></pre>

<p>Você observará que, quando o setTimeout executa as três atualizações, o console mostra &quot;Renderização&quot; apenas uma vez. No React 17, seriam três renderizações separadas.</p>

<h2>Casos Especiais e Controle Manual de Batching</h2>

<h3>Quando Você Quer Renderizar Imediatamente: flushSync</h3>

<p>Ocasionalmente, você pode precisar que uma atualização de estado seja aplicada <strong>imediatamente</strong>, sem aguardar o batching automático. Para isso, use a função <code>flushSync</code> do React.</p>

<pre><code class="language-jsx">import React, { useState } from &#039;react&#039;;

import { flushSync } from &#039;react-dom&#039;;

export function FlushSyncExample() {

const [count, setCount] = useState(0);

const [multiplier, setMultiplier] = useState(1);

console.log(&#039;Renderização&#039;);

const handleFlushSync = () =&gt; {

// A primeira atualização será aplicada imediatamente (renderização 1)

flushSync(() =&gt; {

setCount(prev =&gt; prev + 1);

});

// Neste ponto, o DOM foi atualizado

// A segunda atualização será batched normalmente (renderização 2)

setMultiplier(2);

};

return (

&lt;div&gt;

&lt;p&gt;Count: {count}&lt;/p&gt;

&lt;p&gt;Multiplier: {multiplier}&lt;/p&gt;

&lt;button onClick={handleFlushSync}&gt;Usar flushSync&lt;/button&gt;

&lt;/div&gt;

);

}</code></pre>

<p>O <code>flushSync</code> força a renderização síncrona da atualização envolvida. Use com moderação, pois quebra o batching automático e pode prejudicar a performance se abusado. Situações legítimas incluem sincronização com bibliotecas do DOM ou quando você precisa acessar valores atualizados imediatamente.</p>

<h3>Exemplo Prático: Integração com Biblioteca Externa</h3>

<pre><code class="language-jsx">import React, { useState, useRef } from &#039;react&#039;;

import { flushSync } from &#039;react-dom&#039;;

export function ExternalLibraryIntegration() {

const [inputValue, setInputValue] = useState(&#039;&#039;);

const inputRef = useRef(null);

const handleInputChange = (e) =&gt; {

const value = e.target.value;

// Força a atualização do estado antes de chamar uma função externa

flushSync(() =&gt; {

setInputValue(value);

});

// Agora podemos interagir com APIs externas que dependem do valor atualizado

if (inputRef.current &amp;&amp; value.length &gt; 3) {

// Exemplo: triggar validação ou autocomplete

console.log(&#039;Validação acionada para:&#039;, value);

}

};

return (

&lt;div&gt;

&lt;input

ref={inputRef}

value={inputValue}

onChange={handleInputChange}

placeholder=&quot;Digite algo (3+ caracteres)&quot;

/&gt;

&lt;p&gt;Valor: {inputValue}&lt;/p&gt;

&lt;/div&gt;

);

}</code></pre>

<h2>Impacto na Performance e Boas Práticas</h2>

<p>O batching automático do React 18 oferece ganhos de performance significativos, especialmente em aplicações que executam múltiplas atualizações de estado em contextos assíncronos. Em um teste simples com 10 atualizações de estado simultâneas, você pode esperar uma redução de até 10 vezes no número de renderizações.</p>

<p>Contudo, existem armadilhas comuns que você deve evitar. A primeira é não medir a performance real da sua aplicação — use React DevTools Profiler para identificar gargalos reais antes de otimizar. A segunda é não entender quando usar <code>flushSync</code>; a maioria das aplicações nunca precisará dessa função. A terceira é acumular lógica complexa dentro de event handlers ou callbacks — separe a lógica em custom hooks e funções puras para manter o código limpo e previsível.</p>

<pre><code class="language-jsx">import React, { useState, useCallback } from &#039;react&#039;;

export function BestPracticesExample() {

const [formData, setFormData] = useState({

name: &#039;&#039;,

email: &#039;&#039;,

age: &#039;&#039;

});

const [isSubmitting, setIsSubmitting] = useState(false);

// Separar lógica em funções puras

const updateFormField = useCallback((field, value) =&gt; {

setFormData(prev =&gt; ({

...prev,

[field]: value

}));

}, []);

// Separar lógica de submissão

const handleSubmit = useCallback(async () =&gt; {

setIsSubmitting(true);

try {

// Simular envio

await new Promise(resolve =&gt; setTimeout(resolve, 1000));

console.log(&#039;Dados enviados:&#039;, formData);

// Reset seria agrupado automaticamente

setFormData({ name: &#039;&#039;, email: &#039;&#039;, age: &#039;&#039; });

} finally {

// Ambas as atualizações aqui são batched automaticamente

setIsSubmitting(false);

}

}, [formData]);

return (

&lt;div&gt;

&lt;input

value={formData.name}

onChange={(e) =&gt; updateFormField(&#039;name&#039;, e.target.value)}

placeholder=&quot;Nome&quot;

/&gt;

&lt;input

value={formData.email}

onChange={(e) =&gt; updateFormField(&#039;email&#039;, e.target.value)}

placeholder=&quot;Email&quot;

/&gt;

&lt;input

value={formData.age}

onChange={(e) =&gt; updateFormField(&#039;age&#039;, e.target.value)}

placeholder=&quot;Idade&quot;

/&gt;

&lt;button onClick={handleSubmit} disabled={isSubmitting}&gt;

{isSubmitting ? &#039;Enviando...&#039; : &#039;Enviar&#039;}

&lt;/button&gt;

&lt;/div&gt;

);

}</code></pre>

<h2>Conclusão</h2>

<p>O <strong>Automatic Batching do React 18</strong> resolve um problema histórico: a inconsistência nas atualizações de estado entre contextos síncronos e assíncronos. Agora, múltiplas chamadas <code>setState</code> são agrupadas em uma única renderização automaticamente, independentemente de onde são disparadas — em event handlers, Promises, setTimeout ou callbacks de bibliotecas externas.</p>

<p>O segundo ponto crucial é entender que você geralmente <strong>não precisa fazer nada</strong> para aproveitar essa otimização. O comportamento automático é o padrão, e apenas em casos raros (integração com código legado ou APIs externas sensíveis ao timing) você usará <code>flushSync</code>. Essa é uma melhoria que funciona nos bastidores.</p>

<p>Por último, lembre-se que batching automático não elimina a necessidade de pensar sobre estrutura de componentes, memoização ou derivação de estado. Use React DevTools Profiler para medir o impacto real em sua aplicação. As otimizações mais importantes continuam sendo aquelas no nível arquitetural: decomposição de componentes, evitar state desnecessário e usar reducers para lógica complexa.</p>

<h2>Referências</h2>

<ul>

<li><a href="https://react.dev/blog/2021/06/08/the-next-major-release-of-react#automatic-batching" target="_blank" rel="noopener noreferrer">React 18 Automatic Batching Documentation</a></li>

<li><a href="https://react.dev/blog/2022/03/29/react-v18" target="_blank" rel="noopener noreferrer">React 18 Release Notes - New in React 18</a></li>

<li><a href="https://react.dev/reference/react-dom/flushSync" target="_blank" rel="noopener noreferrer">flushSync API Documentation</a></li>

<li><a href="https://github.com/facebook/react/issues/20090" target="_blank" rel="noopener noreferrer">Understanding React Batching by Dan Abramov</a></li>

<li><a href="https://react.dev/learn/render-and-commit" target="_blank" rel="noopener noreferrer">Concurrent React Rendering Explainer</a></li>

</ul>

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

Comentários

Mais em React & Frontend

O que Todo Dev Deve Saber sobre Hooks para WebSockets: Conexão Reativa e Reconexão Automática
O que Todo Dev Deve Saber sobre Hooks para WebSockets: Conexão Reativa e Reconexão Automática

Entendendo WebSockets e a Necessidade de Hooks Reativos WebSockets estabelece...

O que Todo Dev Deve Saber sobre useRef Avançado: DOM, Valores Mutáveis e Comunicação entre Renders
O que Todo Dev Deve Saber sobre useRef Avançado: DOM, Valores Mutáveis e Comunicação entre Renders

O que é useRef e por que vai além de useState O é um hook do React que retorn...

Controlled vs Uncontrolled Components: Quando Usar Cada Abordagem na Prática
Controlled vs Uncontrolled Components: Quando Usar Cada Abordagem na Prática

O Que São Componentes Controlados e Não Controlados? Antes de mais nada, prec...