<h2>O que é useRef e por que vai além de useState</h2>
<p>O <code>useRef</code> é um hook do React que retorna um objeto mutável cuja propriedade <code>.current</code> persiste entre renders. Diferente de <code>useState</code>, modificar um ref não dispara re-renders. Isso torna o hook perfeito para casos onde você precisa manter valores sem afetar a renderização ou acessar elementos do DOM diretamente.</p>
<p>A razão pela qual <code>useRef</code> é tão poderoso é que ele quebra o ciclo de dados unidirecional do React. Enquanto <code>useState</code> força a filosofia "estado muda → componente re-renderiza", <code>useRef</code> permite armazenar dados que podem ser lidos e escritos sem consequências de renderização. Isso é especialmente útil em cenários onde performance importa ou quando você precisa integrar com APIs imperativos do navegador.</p>
<p>Vamos pensar em um caso prático: um aplicativo que precisa focar um input quando um botão é clicado, ou um contador que incrementa internamente sem atualizar a UI. Esses são cenários perfeitos para <code>useRef</code>. Você verá que, apesar de simples em conceito, a aplicação correta do hook resolve problemas que seriam complicados com <code>useState</code>.</p>
<h2>Acessando e Manipulando o DOM Diretamente</h2>
<h3>Referências a elementos DOM</h3>
<p>A forma mais comum de usar <code>useRef</code> é obter uma referência direta a um elemento do DOM. Você anexa o ref ao atributo <code>ref</code> de um elemento JSX, e então acessa a instância real do DOM via <code>.current</code>.</p>
<pre><code class="language-javascript">import { useRef } from 'react';
function TextInputWithFocus() {
const inputRef = useRef(null);
const handleFocus = () => {
if (inputRef.current) {
inputRef.current.focus();
inputRef.current.style.borderColor = 'blue';
}
};
return (
<>
<input ref={inputRef} type="text" placeholder="Digite algo..." />
<button onClick={handleFocus}>Focar no input</button>
</>
);
}
export default TextInputWithFocus;</code></pre>
<p>Aqui, <code>inputRef.current</code> aponta para o elemento <code><input></code> real do DOM. Quando você clica no botão, a função <code>handleFocus</code> chama o método <code>.focus()</code> diretamente no elemento, sem passar por nenhum sistema de estado. Isso é imperativo puro — você está dizendo ao navegador exatamente o que fazer.</p>
<h3>Integrando com bibliotecas externas</h3>
<p>Muitas bibliotecas JavaScript (como D3, Chart.js ou editor de código) funcionam de forma imperativa. Elas esperam um nó do DOM e controlam tudo internamente. <code>useRef</code> é a ponte perfeita entre o mundo declarativo do React e essas bibliotecas.</p>
<pre><code class="language-javascript">import { useRef, useEffect } from 'react';
import Chart from 'chart.js/auto';
function BarChart() {
const canvasRef = useRef(null);
const chartRef = useRef(null);
useEffect(() => {
if (canvasRef.current && !chartRef.current) {
chartRef.current = new Chart(canvasRef.current, {
type: 'bar',
data: {
labels: ['Jan', 'Fev', 'Mar', 'Abr'],
datasets: [{
label: 'Vendas',
data: [12, 19, 3, 5],
backgroundColor: 'rgba(75, 192, 192, 0.2)',
borderColor: 'rgba(75, 192, 192, 1)',
borderWidth: 1
}]
}
});
}
return () => {
if (chartRef.current) {
chartRef.current.destroy();
}
};
}, []);
return <canvas ref={canvasRef} width="400" height="200"></canvas>;
}
export default BarChart;</code></pre>
<p>O padrão aqui é crucial: criamos a instância da biblioteca só uma vez (verificamos se já existe com <code>!chartRef.current</code>), armazenamos em um ref e limpamos quando o componente é desmontado. Assim evitamos múltiplas instâncias e vazamento de memória.</p>
<h2>Valores Mutáveis e Persistência Entre Renders</h2>
<h3>Armazenando valores sem disparo de re-renders</h3>
<p>Um uso avançado de <code>useRef</code> é manter valores que mudam, mas não precisam atualizar a interface. Imagine um timer que conta internamente enquanto você vê apenas o resultado final quando pressiona um botão. <code>useRef</code> permite isso sem criar renders desnecessários.</p>
<pre><code class="language-javascript">import { useRef, useState } from 'react';
function StopWatch() {
const [displayTime, setDisplayTime] = useState(0);
const timeRef = useRef(0);
const intervalRef = useRef(null);
const isRunningRef = useRef(false);
const handleStart = () => {
if (isRunningRef.current) return;
isRunningRef.current = true;
intervalRef.current = setInterval(() => {
timeRef.current += 1;
}, 1000);
};
const handleStop = () => {
isRunningRef.current = false;
clearInterval(intervalRef.current);
setDisplayTime(timeRef.current);
};
const handleReset = () => {
isRunningRef.current = false;
clearInterval(intervalRef.current);
timeRef.current = 0;
setDisplayTime(0);
};
return (
<div>
<p>Tempo: {displayTime}s</p>
<button onClick={handleStart}>Iniciar</button>
<button onClick={handleStop}>Parar e Exibir</button>
<button onClick={handleReset}>Resetar</button>
</div>
);
}
export default StopWatch;</code></pre>
<p>Note que <code>timeRef.current</code> é incrementado 1000 vezes por segundo internamente, mas o componente não re-renderiza. Apenas quando você clica em "Parar e Exibir" é que <code>setDisplayTime</code> é chamado, disparando um render. Isso é extremamente eficiente: você evita 1000 re-renders desnecessários.</p>
<h3>Tracking de valores anteriores</h3>
<p>Um padrão avançado é usar <code>useRef</code> para rastrear o valor anterior de um estado. Isso é útil quando você precisa saber se algo mudou ou comparar valores entre renders.</p>
<pre><code class="language-javascript">import { useRef, useEffect, useState } from 'react';
function ComponenteComRastreamento() {
const [count, setCount] = useState(0);
const prevCountRef = useRef();
useEffect(() => {
prevCountRef.current = count;
}, [count]);
return (
<div>
<p>Agora: {count}</p>
<p>Antes: {prevCountRef.current}</p>
<p>Mudou: {count !== prevCountRef.current ? 'Sim' : 'Não'}</p>
<button onClick={() => setCount(count + 1)}>Incrementar</button>
</div>
);
}
export default ComponenteComRastreamento;</code></pre>
<p>O <code>useEffect</code> executa <em>após</em> o render, então captura o valor antigo de <code>count</code> em <code>prevCountRef.current</code>. No próximo render, você consegue comparar o novo valor com o anterior. Esse padrão é base para implementar lógicas de "se mudou, faça algo".</p>
<h2>Comunicação Entre Renders e Fluxos de Dados Complexos</h2>
<h3>Sincronizando múltiplos refs e estados</h3>
<p>Em aplicações mais complexas, você pode precisar sincronizar vários refs e estados para manter a lógica coerente. Um exemplo real é um formulário com validação que precisa rastrear valores, erros e estados de envio sem re-renderizar desnecessariamente.</p>
<pre><code class="language-javascript">import { useRef, useState } from 'react';
function AdvancedForm() {
const [submitting, setSubmitting] = useState(false);
const [submitted, setSubmitted] = useState(false);
const formRef = useRef(null);
const validationStateRef = useRef({
name: true,
email: true,
isValid: false
});
const fieldValuesRef = useRef({
name: '',
email: ''
});
const validateField = (name, value) => {
let isValid = true;
if (name === 'name') {
isValid = value.trim().length >= 3;
} else if (name === 'email') {
isValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
}
validationStateRef.current[name] = isValid;
fieldValuesRef.current[name] = value;
// Atualizar validação geral
validationStateRef.current.isValid =
validationStateRef.current.name &&
validationStateRef.current.email;
};
const handleChange = (e) => {
const { name, value } = e.target;
validateField(name, value);
};
const handleSubmit = async (e) => {
e.preventDefault();
if (!validationStateRef.current.isValid) {
alert('Formulário inválido');
return;
}
setSubmitting(true);
try {
// Simular chamada à API
await new Promise(resolve => setTimeout(resolve, 1000));
console.log('Dados enviados:', fieldValuesRef.current);
setSubmitted(true);
// Limpar após sucesso
setTimeout(() => {
setSubmitted(false);
formRef.current?.reset();
fieldValuesRef.current = { name: '', email: '' };
validationStateRef.current = {
name: true,
email: true,
isValid: false
};
}, 2000);
} finally {
setSubmitting(false);
}
};
return (
<form ref={formRef} onSubmit={handleSubmit}>
<div>
<input
type="text"
name="name"
placeholder="Nome"
onChange={handleChange}
/>
{!validationStateRef.current.name && <p style={{ color: 'red' }}>Nome deve ter 3+ caracteres</p>}
</div>
<div>
<input
type="email"
name="email"
placeholder="Email"
onChange={handleChange}
/>
{!validationStateRef.current.email && <p style={{ color: 'red' }}>Email inválido</p>}
</div>
<button
type="submit"
disabled={submitting || !validationStateRef.current.isValid}
>
{submitting ? 'Enviando...' : 'Enviar'}
</button>
{submitted && <p style={{ color: 'green' }}>Formulário enviado com sucesso!</p>}
</form>
);
}
export default AdvancedForm;</code></pre>
<p>Aqui você vê múltiplos refs trabalhando juntos: <code>validationStateRef</code> rastreia a validade, <code>fieldValuesRef</code> armazena os valores do formulário, e <code>formRef</code> permite resetar o formulário. Nada disso dispara re-renders até que seja necessário (quando <code>setSubmitting</code> ou <code>setSubmitted</code> são chamados). Isso mantém o formulário responsivo mesmo com lógica complexa.</p>
<h3>Coordenando fluxos assíncronos</h3>
<p>Um cenário avançado é usar refs para coordenar múltiplas operações assíncronas sem que uma interfira na outra.</p>
<pre><code class="language-javascript">import { useRef, useState, useCallback } from 'react';
function DataFetcher() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const requestIdRef = useRef(null);
const activeRequestsRef = useRef(new Set());
const fetchData = useCallback(async (url) => {
const currentRequestId = Symbol('request');
requestIdRef.current = currentRequestId;
activeRequestsRef.current.add(currentRequestId);
setLoading(true);
try {
const response = await fetch(url);
const result = await response.json();
// Só atualiza o estado se este request ainda é o mais recente
if (requestIdRef.current === currentRequestId) {
setData(result);
}
} catch (error) {
if (requestIdRef.current === currentRequestId) {
setData({ error: error.message });
}
} finally {
activeRequestsRef.current.delete(currentRequestId);
if (activeRequestsRef.current.size === 0) {
setLoading(false);
}
}
}, []);
return (
<div>
<button onClick={() => fetchData('https://jsonplaceholder.typicode.com/posts/1')}>
Fetch Post 1
</button>
<button onClick={() => fetchData('https://jsonplaceholder.typicode.com/posts/2')}>
Fetch Post 2
</button>
{loading && <p>Carregando...</p>}
{data && (
<div>
<h3>{data.title | | 'Erro ao carregar'}</h3> <p>{data.body || data.error}</p>
</div>
)}
</div>
);
}
export default DataFetcher;</code></pre>
<p>Este exemplo mostra race conditions resolvidas com refs. Quando você faz dois requests rapidamente, apenas o mais recente atualiza o estado. O ref <code>requestIdRef</code> rastreia qual é o request "vencedor" e <code>activeRequestsRef</code> monitora todas as requisições ativas. Sem isso, você enfrentaria bugs onde requests antigos sobrescrevem dados novos.</p>
<h2>Conclusão</h2>
<p>Aprendemos que <code>useRef</code> vai muito além de simplesmente acessar o DOM. Primeiro, é uma ferramenta para armazenar valores mutáveis que <strong>não disparam re-renders</strong>, permitindo otimizações drasticamente e facilitando integrações com APIs imperativos. Segundo, refs são excelentes para rastrear estado anterior, sincronizar múltiplos valores e resolver race conditions em operações assíncronas — tudo sem o overhead de renderização. Terceiro, o domínio de <code>useRef</code> avançado o coloca em um nível onde consegue escrever código React altamente eficiente e robusto, sabendo exatamente quando e por que usar refs em vez de estado.</p>
<h2>Referências</h2>
<ul>
<li><a href="https://react.dev/reference/react/useRef" target="_blank" rel="noopener noreferrer">React Official Documentation - useRef</a></li>
<li><a href="https://react.dev/reference/react/useRef" target="_blank" rel="noopener noreferrer">React Hooks API Reference - useRef</a></li>
<li><a href="https://dmitripavlutin.com/react-useref/" target="_blank" rel="noopener noreferrer">Dmitri Pavlutin - React useRef: The Complete Guide</a></li>
<li><a href="https://kentcdodds.com/blog/usehref-vs-usestate" target="_blank" rel="noopener noreferrer">Kent C. Dodds - When to useRef</a></li>
<li><a href="https://web.dev/dom/" target="_blank" rel="noopener noreferrer">Web.dev - Working with the DOM</a></li>
</ul>
<p><!-- FIM --></p>