Tipar useRef no React com TypeScript 2026
useRef tem dois usos no React: referenciar elementos do DOM e guardar valores mutáveis entre renders. Cada um exige tipagem diferente. Aprenda os dois padrões, quando usar null
Por que isso é importante
Tipar useRef no React com TypeScript 2026. useRef tem dois usos no React: referenciar elementos do DOM e guardar valores mutáveis entre renders. Cada um exige tipagem diferente. Aprenda os dois padrões, quando usar null no valor inicial e como o TypeScript trata o .current em cada caso.
Os dois padrões de useRef
O useRef serve pra duas coisas completamente diferentes, e o TypeScript trata cada uma de forma distinta. O primeiro uso é referenciar elementos do DOM -- o famoso <code>ref={meuRef}</code> no JSX. O segundo é guardar valores mutáveis que persistem entre renders sem causar re-render (tipo um timer ID ou valor anterior).
A diferença na tipagem é sutil mas gera muita confusão. Pra DOM refs, você passa <code>null</code> como valor inicial e o TypeScript retorna <code>RefObject</code> com <code>.current</code> readonly. Pra refs mutáveis, você passa o valor inicial sem null (ou inclui null no generic) e recebe <code>MutableRefObject</code> com <code>.current</code> que aceita escrita.
Dá pra lembrar assim: se o React vai atribuir o valor (DOM ref), é readonly. Se você vai atribuir o valor (ref mutável), é mutável. O TypeScript decide isso baseado no tipo do generic vs o tipo do valor inicial.
// Padrão 1: DOM ref (readonly .current)
// null no valor inicial + HTMLElement no generic
const inputRef = useRef<HTMLInputElement>(null);
// inputRef.current é HTMLInputElement | null (readonly)
// Padrão 2: Ref mutável (writable .current)
// Valor inicial do mesmo tipo que o generic
const timerRef = useRef<number | null>(null);
// timerRef.current é number | null (mutável)
const countRef = useRef<number>(0);
// countRef.current é number (mutável)
Passo a passo: tipando useRef corretamente
HTMLInputElement, HTMLDivElement, HTMLButtonElement, etc. Declare useRef<HTMLInputElement>(null) e passe como ref={inputRef}.if (ref.current) ou optional chaining ref.current?.focus() antes de chamar métodos.useRef<number>(0). O .current aceita atribuição direta.useRef<NodeJS.Timeout | null>(null). Inclua null no union do generic, não só no valor inicial.React.forwardRef. Tipo o ref interno com o elemento correto.Exemplos práticos: DOM refs e refs mutáveis
Focus automático com DOM ref
O caso de uso clássico: focar um input quando o componente monta. O tipo preciso do elemento garante que <code>.focus()</code>, <code>.value</code> e outras propriedades apareçam no autocomplete.
import { useRef, useEffect } from "react";
function CampoBusca() {
const inputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
// Checa null porque o ref pode não estar atribuído ainda
inputRef.current?.focus();
}, []);
return (
<input
ref={inputRef}
type="text"
placeholder="Buscar..."
onChange={(e) => console.log(e.target.value)}
/>
);
}
Timer ref com cleanup
Galera, guardar IDs de setTimeout/setInterval no useRef é o padrão certo. Se guardar em variável local, perde a referência entre renders. Se guardar em state, causa re-render desnecessário.
import { useRef, useEffect, useState } from "react";
function Debounce() {
const [busca, setBusca] = useState("");
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
useEffect(() => {
// Limpa timer anterior
if (timerRef.current) {
clearTimeout(timerRef.current);
}
// Cria novo timer
timerRef.current = setTimeout(() => {
console.log("Buscando:", busca);
}, 500);
// Cleanup no unmount
return () => {
if (timerRef.current) {
clearTimeout(timerRef.current);
}
};
}, [busca]);
return (
<input
value={busca}
onChange={(e) => setBusca(e.target.value)}
placeholder="Digite pra buscar..."
/>
);
}
Ref de valor anterior
Outro padrão comum: guardar o valor anterior de um state ou prop. O ref atualiza depois do render, então sempre tem o valor da renderização anterior.
import { useRef, useEffect, useState } from "react";
function usePrevious<T>(valor: T): T | undefined {
const ref = useRef<T | undefined>(undefined);
useEffect(() => {
ref.current = valor;
});
return ref.current;
}
// Uso
function Contador() {
const [count, setCount] = useState(0);
const prevCount = usePrevious(count);
return (
<div>
<p>Atual: {count}</p>
<p>Anterior: {prevCount ?? "nenhum"}</p>
<button onClick={() => setCount(c => c + 1)}>
Incrementar
</button>
</div>
);
}
forwardRef com TypeScript
Quando você cria um componente que precisa expor seu ref pro componente pai, usa <code>forwardRef</code>. O TypeScript exige dois generics: o tipo do ref e o tipo das props.
import { forwardRef } from "react";
interface InputCustomProps {
label: string;
erro?: string;
}
const InputCustom = forwardRef<HTMLInputElement, InputCustomProps>(
({ label, erro }, ref) => {
return (
<div>
<label>{label}</label>
<input ref={ref} className={erro ? "input-erro" : ""} />
{erro && <span>{erro}</span>}
</div>
);
}
);
// Uso no componente pai
function Formulario() {
const nomeRef = useRef<HTMLInputElement>(null);
return <InputCustom ref={nomeRef} label="Nome" />;
}
Erros comuns com useRef tipado
Evite essas ciladas
Esquecer null no valor inicial do DOM ref: useRef<HTMLInputElement>() sem null gera tipo errado. Sempre passe null: useRef<HTMLInputElement>(null).
Tentar atribuir .current em DOM ref: DOM refs são readonly. Se precisa de um ref mutável com elemento DOM, inclua null no generic: useRef<HTMLElement | null>(null).
Não checar null antes de acessar .current: DOM refs são null até o componente montar. Acesso direto sem check quebra em runtime.
Usar useRef pra state que precisa de re-render: useRef não causa re-render quando muda. Se a UI depende do valor, use useState.
Tipo HTML genérico demais: useRef<HTMLElement> funciona, mas perde métodos específicos. useRef<HTMLInputElement> dá acesso a .value, .focus(), .select(), etc.
Checklist: useRef tipado corretamente
Checklist de useRef + TypeScript
Refs sem mistério
useRef tipado corretamente destranca cenários avançados: focus management, animações, integrações com libs externas e custom hooks poderosos. No CrazyStack, você pratica tudo isso em projetos reais -- do formulário com validação ao player de vídeo customizado. Domine hooks com tipagem profissional.