<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 'react';
export function SyncBatchingExample() {
const [count, setCount] = useState(0);
const [isLoading, setIsLoading] = useState(false);
const [message, setMessage] = useState('');
console.log('Renderização');
const handleClick = () => {
// Todas essas três atualizações serão agrupadas em uma ÚNICA renderização
setCount(prev => prev + 1);
setIsLoading(true);
setMessage('Atualizando...');
};
return (
<div>
<p>Count: {count}</p>
<p>Loading: {isLoading ? 'Sim' : 'Não'}</p>
<p>Message: {message}</p>
<button onClick={handleClick}>Atualizar Estado</button>
</div>
);
}</code></pre>
<p>Quando você clica no botão acima, verá "Renderização" 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 'react';
export function AsyncBatchingExample() {
const [count, setCount] = useState(0);
const [isLoading, setIsLoading] = useState(false);
const [data, setData] = useState(null);
console.log('Renderização');
const handleAsyncUpdate = async () => {
// Iniciar carregamento
setIsLoading(true);
try {
// Simular chamada à API
const response = await fetch('https://jsonplaceholder.typicode.com/posts/1');
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 => prev + 1);
} catch (error) {
setIsLoading(false);
setData(null);
}
};
return (
<div>
<p>Count: {count}</p>
<p>Loading: {isLoading ? 'Carregando...' : 'Concluído'}</p>
{data && <p>Data Title: {data.title}</p>}
<button onClick={handleAsyncUpdate}>Carregar Dados</button>
</div>
);
}</code></pre>
<p>Quando você clica em "Carregar Dados", 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 'react';
export function TimeoutBatchingExample() {
const [firstValue, setFirstValue] = useState('');
const [secondValue, setSecondValue] = useState('');
const [thirdValue, setThirdValue] = useState('');
console.log('Renderização');
const handleComplexUpdate = () => {
// Simular múltiplas operações assíncronas com setTimeout
setTimeout(() => {
// React 18 agrupa essas três atualizações em uma renderização
setFirstValue('Valor 1');
setSecondValue('Valor 2');
setThirdValue('Valor 3');
}, 1000);
};
return (
<div>
<p>1: {firstValue}</p>
<p>2: {secondValue}</p>
<p>3: {thirdValue}</p>
<button onClick={handleComplexUpdate}>
Iniciar Atualização com Delay
</button>
</div>
);
}</code></pre>
<p>Você observará que, quando o setTimeout executa as três atualizações, o console mostra "Renderização" 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 'react';
import { flushSync } from 'react-dom';
export function FlushSyncExample() {
const [count, setCount] = useState(0);
const [multiplier, setMultiplier] = useState(1);
console.log('Renderização');
const handleFlushSync = () => {
// A primeira atualização será aplicada imediatamente (renderização 1)
flushSync(() => {
setCount(prev => prev + 1);
});
// Neste ponto, o DOM foi atualizado
// A segunda atualização será batched normalmente (renderização 2)
setMultiplier(2);
};
return (
<div>
<p>Count: {count}</p>
<p>Multiplier: {multiplier}</p>
<button onClick={handleFlushSync}>Usar flushSync</button>
</div>
);
}</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 'react';
import { flushSync } from 'react-dom';
export function ExternalLibraryIntegration() {
const [inputValue, setInputValue] = useState('');
const inputRef = useRef(null);
const handleInputChange = (e) => {
const value = e.target.value;
// Força a atualização do estado antes de chamar uma função externa
flushSync(() => {
setInputValue(value);
});
// Agora podemos interagir com APIs externas que dependem do valor atualizado
if (inputRef.current && value.length > 3) {
// Exemplo: triggar validação ou autocomplete
console.log('Validação acionada para:', value);
}
};
return (
<div>
<input
ref={inputRef}
value={inputValue}
onChange={handleInputChange}
placeholder="Digite algo (3+ caracteres)"
/>
<p>Valor: {inputValue}</p>
</div>
);
}</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 'react';
export function BestPracticesExample() {
const [formData, setFormData] = useState({
name: '',
email: '',
age: ''
});
const [isSubmitting, setIsSubmitting] = useState(false);
// Separar lógica em funções puras
const updateFormField = useCallback((field, value) => {
setFormData(prev => ({
...prev,
[field]: value
}));
}, []);
// Separar lógica de submissão
const handleSubmit = useCallback(async () => {
setIsSubmitting(true);
try {
// Simular envio
await new Promise(resolve => setTimeout(resolve, 1000));
console.log('Dados enviados:', formData);
// Reset seria agrupado automaticamente
setFormData({ name: '', email: '', age: '' });
} finally {
// Ambas as atualizações aqui são batched automaticamente
setIsSubmitting(false);
}
}, [formData]);
return (
<div>
<input
value={formData.name}
onChange={(e) => updateFormField('name', e.target.value)}
placeholder="Nome"
/>
<input
value={formData.email}
onChange={(e) => updateFormField('email', e.target.value)}
placeholder="Email"
/>
<input
value={formData.age}
onChange={(e) => updateFormField('age', e.target.value)}
placeholder="Idade"
/>
<button onClick={handleSubmit} disabled={isSubmitting}>
{isSubmitting ? 'Enviando...' : 'Enviar'}
</button>
</div>
);
}</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><!-- FIM --></p>