Refs e o DOM
React constrói e atualiza o DOM para você. Você descreve como a tela deve se parecer, e os elementos reais são responsabilidade do React. Um pequeno número de tarefas ainda precisa do elemento em si: focar um input depois que um botão é clicado, fazer scroll de uma mensagem para a vista, medir a largura de uma caixa, reproduzir um vídeo. Nenhuma delas pode ser expressa como um pedaço de JSX, porque cada uma age sobre um nó em vez de descrever um. Uma ref é a válvula de escape para elas. Ela dá a um componente um jeito de manter uma referência a um elemento real do DOM e chamar métodos nele diretamente.
Anexando uma ref
Uma ref começa com o hook useRef, e você passa o resultado para um elemento através do atributo ref:
import { useRef } from 'react'
function SearchBar() {
const inputRef = useRef(null)
function handleFocus() {
inputRef.current.focus()
}
return (
<>
<input ref={inputRef} />
<button onClick={handleFocus}>Focus</button>
</>
)
}useRef(null) retorna um objeto com uma única propriedade, current, contendo o que você passou como valor inicial. Você sempre passa um, e null é o ponto de partida convencional para uma ref de DOM, já que não há nó naquele momento.
Escrever ref={inputRef} é a instrução que React precisa. Quando React coloca aquele <input> na tela, ele define inputRef.current para o nó do DOM. A partir daí inputRef.current é o elemento input em si, com cada método e propriedade que o navegador fornece, então inputRef.current.focus() dentro do handler de clique move o cursor para o campo. React mantém a atribuição atualizada para você: se o elemento for removido da tela, React define current de volta para null.
Fazendo scroll de um nó para a vista
Focar é uma tarefa. Scroll é a outra que você vai encontrar cedo, geralmente em algo como um log de chat que deve pular para a mensagem mais recente. Essa tarefa roda depois do render, então ela faz par com useEffect:
import { useRef, useEffect } from 'react'
function MessageList({ messages }) {
const bottomRef = useRef(null)
useEffect(() => {
bottomRef.current.scrollIntoView({ behavior: 'smooth' })
}, [messages])
return (
<div>
{messages.map(message => (
<p key={message.id}>{message.text}</p>
))}
<div ref={bottomRef} />
</div>
)
}O <div> vazio no final existe apenas como algo para fazer scroll até, que é um truque comum e perfeitamente razoável. O effect roda quando messages muda, e naquele ponto React já colocou as novas mensagens na tela e apontou bottomRef.current para aquele div final.
Mudar uma ref nunca dispara um render
State e refs sobrevivem de um render para o próximo, e aí a semelhança termina. Chamar um state setter pede a React para renderizar o componente novamente com o novo valor. Atribuir a ref.current muda o valor silenciosamente, e a tela fica exatamente como estava.
Esse silêncio é o ponto todo. Uma ref segura algo que a UI não exibe: um nó do DOM, um ID de timer, uma flag que sua lógica de renderização nunca lê. Renderizar novamente quando um desses muda custaria um redesenho que não poderia mudar um pixel sequer.
Tem uma segunda consequência que vale manter em mente. Porque nada precisa ser agendado, o valor em ref.current pode ser lido no instante depois que você o atribui. Uma variável de state é fixa por todo um render e só muda no próximo, como State aborda. Uma ref se comporta como uma caixa mutável comum que você pode ler e escrever a qualquer momento.
Quando uma ref é o instinto errado
A linha divisória é se o valor acaba na tela. Texto em um cabeçalho, um número em um badge, se um painel está aberto: tudo isso pertence a state, porque a exibição tem que mudar quando o valor muda. Coloque isso em uma ref e a atualização passa despercebida: o valor muda, e a UI continua mostrando tudo que renderizou por último.
Acessar o DOM para mudar o que é exibido é o mesmo instinto um passo além. Definir node.textContent manualmente, ou alternar uma classe diretamente, parece funcionar, e então o próximo render desfaz, porque React re-aplica tudo que seu JSX descreve. Refs cobrem duas coisas: ler e comandar um nó, ou seja, focar nele, fazer scroll nele, medir, reproduzir, e reter valores que sobrevivem entre renders sem nunca serem exibidos. Para qualquer coisa que o usuário leia na tela, descreva em JSX e dirija com state.
Nota de versão
Antes do React 19, componentes de função não podiam receber uma prop ref de jeito nenhum. Passar uma através para um elemento dentro exigia envolver o componente em forwardRef. React 19 entrega ref a componentes de função como uma prop ordinária, então forwardRef é desnecessário em código novo e está deprecado. Muito tempo atrás, componentes de classe criavam refs com React.createRef() e as liam de this. O hook useRef cobre ambos os papéis em componentes de função.
useRef(null), coloque o resultado em um elemento com ref={inputRef}, e React preenche inputRef.current com o nó real do DOM uma vez que está na tela. Aí você pode fazer coisas com ele, como inputRef.current.focus(). Recorra a uma ref quando você precisa focar, fazer scroll, ou medir algo. Qualquer coisa que a pessoa usando seu app leia na tela pertence a state.
Próximo: Hooks, a família mais ampla que useState, useEffect, e useRef pertencem.

