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.rendereReactDOM.hydratedesapareceram — passam acreateRootehydrateRoot.defaultPropsem componentes de função: passam a parâmetros por defeito.propTypesdeixou 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@19A 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 ./src8. 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.