FNSP Web Page

Do React 18 para o 19: o que se ganha, e o que parte pelo caminho

O React 19 saiu em dezembro de 2024, e tem uma particularidade: a lista do que parte é tão interessante como a das novidades. A maior parte das aplicações atualiza sem drama — e as que não atualizam sem drama falham quase sempre pelas mesmas cinco razões.

1. O ref passa a ser uma prop como as outras

É a alteração que mais linhas apaga a quem mantém componentes.

// Antes
const Campo = forwardRef(function Campo(props, ref) {
  return <input ref={ref} {...props} />;
});

// React 19
function Campo({ ref, ...props }) {
  return <input ref={ref} {...props} />;
}

O forwardRef continua a funcionar, mas está marcado como obsoleto.

2. Um ref pode agora limpar-se a si próprio

<div
  ref={(no) => {
    const observador = new ResizeObserver(medir);
    observador.observe(no);
    return () => observador.disconnect();   // limpeza
  }}
/>

Guarde este pormenor: é dele que vem uma das armadilhas da migração, mais abaixo.

3. Duas simplificações pequenas e bem-vindas

const Tema = createContext('claro');

// Antes: <Tema.Provider value="escuro">
<Tema value="escuro">{filhos}</Tema>

E os metadados do documento podem ser escritos onde fizerem falta — o React sobe-os para o head sozinho, o que dispensa uma biblioteca inteira só para isso:

function Artigo({ artigo }) {
  return (
    <article>
      <title>{artigo.titulo}</title>
      <meta name="description" content={artigo.resumo} />
      <h1>{artigo.titulo}</h1>
    </article>
  );
}

4. As Actions, que é a novidade grande

Mudam a forma de escrever formulários. Um form passa a aceitar uma função no action, e o React trata do resto.

function Comentario() {
  const [estado, accao, pendente] = useActionState(
    async (anterior, dados) => {
      const erro = await gravar(dados.get('texto'));
      return erro ?? 'Comentário enviado.';
    },
    null
  );

  return (
    <form action={accao}>
      <textarea name="texto" />
      <button disabled={pendente}>Enviar</button>
      {estado && <p>{estado}</p>}
    </form>
  );
}

O que vem de graça: o estado de pendente, o resultado, e a garantia de que uma segunda submissão não passa à frente da primeira. Ao lado, o useFormStatus deixa um botão saber que o formulário à volta dele está a submeter sem ninguém lhe passar props, e o useOptimistic mostra o resultado antes de ele chegar.

5. A armadilha que ninguém conta: o formulário limpa-se sozinho

Esta apanhou-me neste site, e apanhou-me em cinco diálogos ao mesmo tempo. Quando se passa uma função ao action, o React toma conta do formulário — e tomar conta inclui limpá-lo assim que a função devolve. Inclusive quando ela devolve um erro.

O resultado prático: escrever um artigo inteiro, esquecer a etiqueta, e ver o título e o texto desaparecerem ao mesmo tempo que aparece a mensagem «escolha pelo menos uma etiqueta».

// O React limpa o formulário quando a acção devolve, erro incluído
<form action={guardar}>

// O mesmo, sem limpar
<form
  onSubmit={(evento) => {
    evento.preventDefault();
    guardar(new FormData(evento.currentTarget));
  }}
>

Se o formulário for curto e a submissão não puder ser recusada, o action é melhor e mais limpo. Havendo validação capaz de dizer que não, o onSubmit é o que evita o desastre.

6. O que parte

  • ReactDOM.render e ReactDOM.hydrate desapareceram — passam a createRoot e hydrateRoot.

  • defaultProps em componentes de função: passam a parâmetros por defeito.

  • propTypes deixou de ser lido. Quem os tinha, tem TypeScript ou nada.

  • Refs em texto (ref="campo") e o contexto antigo (contextTypes).

  • ReactDOM.findDOMNode.

E a que dá mais trabalho, precisamente por ser pequena. Lembra-se de os refs poderem devolver uma limpeza? Então uma seta com retorno implícito passou a devolver uma:

// Erro no 19: a seta devolve o valor da atribuição, e o React
// passa a interpretar isso como uma função de limpeza
<input ref={(no) => (this.campo = no)} />

// Duas chavetas, e o problema desaparece
<input ref={(no) => { this.campo = no; }} />

São dois caracteres, e aparece dezenas de vezes num projeto grande. Na mesma linha, o useRef() passou a exigir um argumento: useRef(undefined).

7. Como fazer a migração sem sofrer

# 1. Primeiro o 18.3.1, que é o 18 com avisos sobre tudo aquilo que vai partir
npm install [email protected] [email protected]

# 2. Corrigir tudo aquilo que a consola disser

# 3. Só depois disto, o salto
npm install react@19 react-dom@19

A parte mecânica está resolvida por codemods oficiais — vale sempre mais do que uma busca por texto:

npx codemod@latest react/19/migration-recipe
npx types-react-codemod@latest preset-19 ./src

8. E depois do 19.0

A linha do 19 continuou a mexer-se. No 19.2 apareceram o Activity, que esconde um pedaço da árvore sem o desmontar nem lhe perder o estado, e o useEffectEvent, para tirar de um efeito aquilo que ele lê mas de que não quer depender.

Isto muda depressa: antes de contar com qualquer uma delas, confirme as notas de lançamento da versão que vai mesmo instalar.

O resumo

Quase tudo o que parte cai em duas gavetas: a API de arranque, e coisas que estão obsoletas desde 2018. Se o projeto já andava razoavelmente em dia, a migração é uma tarde. Se não andava, o 18.3.1 diz exatamente o que falta antes de se dar o salto — e é por aí que se começa, não pelo npm install react@19.

Comentários

Ainda ninguém comentou este artigo.

Voltar ao blog

Gostávamos de saber quantas pessoas visitam o site, com o Google Analytics. Sem a sua autorização não corre nada. Política de Privacidade.