Menu

forwardRef no React: passar refs e ref como prop no 19

forwardRef permite que um componente receba uma ref do pai e a conecte a um nó do DOM dentro dele. No React 19 os componentes de função recebem ref como uma prop normal, então código novo não precisa mais de forwardRef. Veja as duas versões funcionando, além de useImperativeHandle e os tipos no TypeScript.

Esta página tem editores executáveis - edite, execute e veja a saída na hora.

forwardRef permite que um componente pai passe uma ref através do seu componente até um elemento do DOM dentro dele, para que o pai possa chamar focus(), scrollIntoView() e métodos parecidos nesse elemento. No React 19 você não precisa mais dele: componentes de função recebem ref como uma prop comum. O forwardRef continua funcionando, e você vai vê-lo na maior parte do código existente, então esta página mostra os dois.

forwardRef recebe uma função de renderização com dois argumentos: props e a ref que o pai passou. A ref não está dentro de props; ela chega separada. Clique em Focus e o console confirma que inputRef.current é o nó <input> de verdade.

O jeito do React 19: ref é uma prop

Desde o React 19 um componente de função recebe ref nas props como qualquer outra prop. Sem wrapper, sem segundo argumento:

Digite algo e clique em Log value: o pai lê o texto atual do input direto do nó do DOM. Apague ref={ref} do <input> e clique em Focus: o console mostra um erro, porque inputRef.current agora é null.

forwardRef (todas as versões)Prop ref (React 19+)
Como o componente recebe a refSegundo argumento, (props, ref)props.ref
Precisa de wrapperSimNão
Funciona com useImperativeHandleSimSim
SituaçãoFunciona, descontinuação planejadaO jeito de escrever código novo

Uma ref que o filho ignora continua null

Passar ref para um componente não faz nada sozinho. O componente precisa colocá-la em um elemento. Se não colocar, ref.current continua null:

O console mostra Forgetful ref: null e Careful ref: INPUT. Esse é o motivo mais comum para "ref.current is null" em um componente personalizado. Antes do React 19, Careful também seria null a menos que estivesse envolvido em forwardRef, já que a ref nunca era entregue à função.

useImperativeHandle: exponha uma API menor

Dar ao pai o nó do DOM inteiro significa que ele pode fazer qualquer coisa com ele: mudar estilos, remover filhos, ler valores que você queria manter privados. O useImperativeHandle permite que você decida o que ref.current contém:

O último botão registra ["focus", "clear"]: o pai recebe esses dois métodos e nada mais, não o nó do input. Adicione um método select() ao objeto e um botão que o chame, e o pai ganha exatamente mais uma capacidade.

O filho mantém a própria ref, inputRef, para o input real, e o handle a envolve. O terceiro argumento é um array de dependências, como o do useEffect: o handle é reconstruído quando esses valores mudam.

Use isso com moderação. A maioria das coisas que um pai quer (abrir, fechar, mostrar um erro) fica melhor como props, como isOpen ou error. Métodos imperativos são para ações que não têm uma prop natural: focar, rolar, tocar um vídeo, iniciar uma animação.

Usando a ref dentro do filho também

Às vezes o filho precisa do mesmo nó do DOM para o próprio trabalho, por exemplo para medi-lo ou focá-lo depois de um erro, enquanto o pai também guarda uma ref para ele. Um atributo ref só aceita um valor, então junte as duas com uma ref callback:

function AutoGrowTextarea({ ref, ...props }) {
    const localRef = useRef(null);

    function setRefs(node) {
        localRef.current = node;
        if (typeof ref === 'function') ref(node);
        else if (ref) ref.current = node;
    }

    return <textarea ref={setRefs} {...props} />;
}

A ref do pai pode ser um objeto de useRef ou uma função, então trate os dois casos. Quando o pai só precisa de algumas ações, o useImperativeHandle acima é a escolha mais limpa, porque o filho guarda o nó para si.

Passando uma ref por várias camadas

Uma ref viaja um componente de cada vez. Se Form renderiza Field, que renderiza TextInput, que renderiza o <input>, cada um desses componentes precisa repassar ref para o próximo. No React 19 isso é mais uma prop para repassar (<TextInput ref={ref} />); com forwardRef, cada camada precisava ser envolvida. Espalhar as props ({...props}) não a leva em versões antigas, porque antes do React 19 a ref nunca fazia parte de props.

Componentes de design system são onde isso costuma importar: um Button, Input ou Select que envolve um elemento nativo deve repassar a ref, para que o app que o usa possa focá-lo, medi-lo ou entregá-lo a uma biblioteca que posiciona popovers.

Migrando do forwardRef

A mudança é mecânica: remova o wrapper e leia ref das props.

// Before
const Button = forwardRef(function Button({ variant, ...props }, ref) {
    return <button ref={ref} className={variant} {...props} />;
});

// After (React 19)
function Button({ variant, ref, ...props }) {
    return <button ref={ref} className={variant} {...props} />;
}

Não há pressa. O forwardRef continua funcionando no React 19, e uma biblioteca que precisa suportar o React 18 tem que mantê-lo, porque o React 18 não passa ref como prop. Componentes de classe não são afetados: uma ref em um componente de classe continua apontando para a instância do componente.

TypeScript

Com forwardRef, os argumentos de tipo vêm nesta ordem: primeiro o tipo da ref, depois as props:

import { forwardRef } from 'react';

type FancyInputProps = { label: string };

const FancyInput = forwardRef<HTMLInputElement, FancyInputProps>(
    function FancyInput({ label }, ref) {
        return <input ref={ref} aria-label={label} />;
    }
);

No React 19, tipe ref como qualquer outra prop. ComponentProps<'input'> já a inclui:

import { useImperativeHandle, useRef, type ComponentProps, type Ref } from 'react';

function FancyInput(props: ComponentProps<'input'>) {
    return <input {...props} />;
}

type SearchHandle = { focus: () => void; clear: () => void };

function SearchBox({ ref }: { ref?: Ref<SearchHandle> }) {
    const inputRef = useRef<HTMLInputElement>(null);
    useImperativeHandle(ref, () => ({
        focus: () => inputRef.current?.focus(),
        clear: () => {
            if (inputRef.current) inputRef.current.value = '';
        },
    }));
    return <input ref={inputRef} />;
}

// In the parent
const searchRef = useRef<SearchHandle>(null);

A página do useRef trata das refs em si: acesso ao DOM, valores que persistem sem renderizar e ref callbacks. Para tipar componentes em geral, veja React com TypeScript.

Perguntas frequentes

O que o forwardRef faz no React?

Ele envolve um componente de função para que uma ref dada pelo pai chegue ao componente como segundo argumento, (props, ref). O componente então coloca essa ref em um nó do DOM, e o pai pode chamar métodos como focus() nele.

O forwardRef foi descontinuado no React 19?

Ainda não, e continua funcionando. O React 19 passa ref para componentes de função como uma prop comum, então código novo não precisa dele, e a equipe do React disse que planeja descontinuar o forwardRef em uma versão futura.

Por que minha ref é null em um componente personalizado?

O componente recebeu a ref mas não a colocou em nenhum elemento. Conecte-a a um nó do DOM lá dentro: <input ref={ref} />. Antes do React 19 a ref nem era passada, a menos que o componente estivesse envolvido em forwardRef.

Para que serve o useImperativeHandle?

Ele substitui o que o pai vê em ref.current. Em vez do nó do DOM inteiro, você retorna um objeto só com os métodos que escolher, como focus e clear.

Componentes de classe recebem ref como prop no React 19?

Não. Uma ref em um componente de classe continua apontando para a instância do componente. A mudança vale só para componentes de função.

Ilustração das linguagens de programação do Coddy

Aprenda a programar com o Coddy

COMEÇAR