hydrateRoot permite que você integre componentes React em um nó no DOM do navegador, cujo conteúdo HTML foi previamente gerado por react-dom/server.

const root = hydrateRoot(domNode, reactNode, options?)

Referências

hydrateRoot(domNode, reactNode, options?)

Use hydrateRoot para “conectar” o React ao HTML existente que já foi renderizado pelo React do lado do servidor.

import { hydrateRoot } from 'react-dom/client';

const domNode = document.getElementById('root');
const root = hydrateRoot(domNode, reactNode);

React irá anexar o HTML existente dentro do domNode, e assumir a gestão do DOM dentro dele. Um aplicativo completamente construído com React comumente só terá uma única chamada do hydrateRoot para seu componente raiz.

Veja mais exemplos abaixo.

Parâmetros

  • domNode: Um elemento DOM que foi renderizado como elemento raiz no servidor.

  • reactNode: O “nó do React” usado para renderizar o HTML existente. Frequentemente será uma parte do JSX como <App /> que foi renderizado com um método ReactDOM Server como renderToPipeableStream(<App />).

  • opcional options: Um objeto com opções para a raiz do React.

    • Canary only opcional onCaughtError: Callback disparado quando o React intercepta um ero no Error Boundary. Vem com o error interceptado pelo Error Boundary, e um objeto errorInfo contendo o componentStack.
    • Canary only opcional onUncaughtError: Callback disparado quando um erro é lançado e não interceptado por um Error Boundary. Vem com o error que foi lançado e um objeto errorInfo contendo o componentStack.
    • opcional onRecoverableError: Callback disparado quando o React se recupera automaticamente de erros. Vem com o error que o React lançou, e um objeto errorInfo contendo o componentStack. Alguns erros recuperáveis podem incluir a causa original do erro como error.cause.
    • opcional identifierPrefix: Um prefixo de texto que o React usa para IDs gerados por useId. Útil para evitar conflitos quando múltiplas raizes são usadas na mesma página. Precisa ser o mesmo prefixo usado no servidor.

Retornos

hydrateRoot retorna um objeto com dois métodos: render and unmount.

Ressalvas

  • hydrateRoot() espera que o conteúdo renderizado seja idêntico ao conteúdo renderizado pelo servidor. Você deve tratar diferenças como erros e corrigí-las.
  • No modo desenvolvedor, o React avisa sobre as diferenças na hidratação. Não há garantias de que as diferenças de atributos serão corrigidas em caso de incompatibilidades. Isso é importante por questões de performance porque na maioria dos aplicativos, diferenças são raras, e, portanto, validar todas as marcações seria proibitivamente caro.
  • Você provavelmente terá apenas uma chamada hydrateRoot no seu aplicativo. Se você tiver um framework, ele pode fazer essa chamada para você.
  • Se a sua aplicação é redenrizada pelo cliente sem ter HTML renderizado ainda, usar hydrateRoot() não é suportado. Use createRoot() alternativamente.

root.render(reactNode)

Chame root.render para atualizar um componente React dentro de uma raiz hidratada do React em um elemento do DOM do navegador.

root.render(<App />);

React atualizará <App /> no root hidratado.

Veja mais exemplos abaixo.

Parâmetros

  • reactNode: Um “nó React” que você quer atualizar. Será frequentemente uma parte do JSX como <App />, mas vocẽ pode passar também um elemento React construído com createElement(), uma string, um número, null, or undefined.

Retornos

root.render retorna undefined.

Ressalvas

  • Se você chamar root.render antes do final da hidratação da raiz, o React irá limpar todo o conteúdo HTML existente renderizado no servidor e substituirá por todo conteúdo da raiz renderizada no cliente.

root.unmount()

Chame root.unmount para desmontar uma árvore renderizada dentro da raiz do React.

root.unmount();

Um aplicativo completamente construído com React usualmente não precisará de nenhuma chamada para root.unmount.

Isso é mais útil se o nó DOM da raiz do React (ou qualquer dos seus ascendentes) pode ser removido do DOM por outro código. Por examplo, imagine um painel de abas do jQuery que remove abas inativas do DOM. Se a aba for removida, tudo dentro dela (incluindo raízes React internas) seria removido do DOM também. Você precisa dizer para o React “parar” de gerenciar os conteúdos das raízes removidas chamando root.unmount. Senão, os componentes dentro da raiz removida não limpará nem liberará os recursos como assinaturas.

Chamar root.unmount desmontará todos os componente da raiz e “desconectará” o React do nó raiz do DOM, incluindo quaisquer manipuladores de evento ou state na árvore.

Parâmetros

root.unmount não aceita nenhum parâmetro.

Retornos

root.unmount retorna undefined.

Ressalvas

  • Chamar root.unmount desmontará todos os componentes na árvore e “desconectará” o React do nó raiz do DOM.

  • Depois de chamar root.unmount você não pode chamar root.render novamente para a raiz. Tentativas de chamar root.render com uma raiz desmontada lançará um “Cannot update an unmounted root” erro.


Utilização

Hidratando HTML renderizado pelo servidor

Se a sua aplicação HTML foi renderizada por react-dom/server, você precisa hidratar ela no cliente.

import { hydrateRoot } from 'react-dom/client';

hydrateRoot(document.getElementById('root'), <App />);

Isso hidratará o HTML do servidor dentro do nó DOM do navegador com o componente React para a sua aplicação. Usualmente, você fará isso uma vez ao iniciar. Se você usa um framework, ele poderá fazer isso para você por trás das cenas.

Para hidratar sua aplicação, React “conectará” a lógica dos seus componentes ao HTML gerado no início pelo servidor. A hidratação transforma o snapshot inicial do HTML do servidor em uma aplicação completa e interativa rodando no navegador.

import './styles.css';
import { hydrateRoot } from 'react-dom/client';
import App from './App.js';

hydrateRoot(
  document.getElementById('root'),
  <App />
);

Você não precisará chamar hydrateRoot novamente ou chamar em mais lugares. Desse ponto em diante, o React gerenciará o DOM de sua aplicação. Para atualizar a UI, seu componente irá usar state agora.

Pitfall

A árvore react que você passou para hydrateRoot precisa produzir a mesma saída que produziu no servidor.

Isso é importante para experiência do usuário. O usuário passará algum tempo procurando no HTML gerado pelo servidor antes do seu código JavaScript carregar. A renderização do servidor cria uma ilusão que o aplicativo carregou rápido, mostrando o snapshot HTML da sua saída. Mostrar de repente um conteúdo diferente quebra essa ilusão. Por isso a saída renderizada do servidor precisa ser compatível com a saída inicial renderizada do cliente.

As causas mais comuns que levam a erros de hidratação incluem:

  • Espaços extras (como caractere de nova linha) ao redor do HTML gerado pelo React na raiz do nó.
  • Usar comparações como typeof window !== 'undefined' na sua lógica de renderização.
  • Usar API’s específicas do navegador como window.matchMedia na sua lógica de renderização.
  • Renderizar diferentes dados no servidor e no cliente.

O React se recupera de alguns erros de hidratação, mas você precisa corrigí-los como os outros erros. No melhor caso, ele ficará lento; no pior caso, manipuladores de eventos podem ser conectados a elementos errados.


Hidratando um documento inteiro

Aplicações totalmente construídas com React podem renderizar o documento inteiro como JSX, incluindo o <html> tag:

function App() {
return (
<html>
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="/styles.css"></link>
<title>My app</title>
</head>
<body>
<Router />
</body>
</html>
);
}

Para hidratar o documento inteiro, passe o document global como primeiro argumento para hydrateRoot:

import { hydrateRoot } from 'react-dom/client';
import App from './App.js';

hydrateRoot(document, <App />);

Suprimindo erros inevitáveis ​​de incompatibilidade de hidratação

Se um simples atributo do elemento ou conteúdo de texto inevitavelmente conter diferenças entre o servidor e o cliente (por examplo, um timestamp), você poderá silenciar o aviso de diferença de hidratação.

Para silenciar avisos de hidratação em um elemento, adicione suppressHydrationWarning={true}:

export default function App() {
  return (
    <h1 suppressHydrationWarning={true}>
      Current Date: {new Date().toLocaleDateString()}
    </h1>
  );
}

Somente funciona em um nível de profundidade, e é para ser usado como uma saída de emergência. Não abuse desse recurso. A menos que seja conteúdo de texto, o React ainda não tentará corrigi-lo, portanto pode permanecer inconsistente até atualizações futuras.


Tratando diferenças de conteúdo entre cliente e servidor

Se você intecionalmente precisa renderizar algo diferente no servidor e no cliente, você pode fazer uma renderização de dois passos. Componentes que renderizam algo diferente no cliente pode ler um state variable como isClient, o qual você pode definir como true em um Effect:

import { useState, useEffect } from "react";

export default function App() {
  const [isClient, setIsClient] = useState(false);

  useEffect(() => {
    setIsClient(true);
  }, []);

  return (
    <h1>
      {isClient ? 'Is Client' : 'Is Server'}
    </h1>
  );
}

Dessa forma a renderização inicial passará a renderizar o mesmo conteúdo do servidor, evitando diferenças, mas um passo adicional acontecerá de forma síncrona logo após a hidratação.

Pitfall

Essa abordagem deixa a hidratação mais vagarosa porque seus componentes terão que renderizar duas vezes. Fique atento a experiência do usuário com conexões lentas. O código JavaScript pode atrasar significantemente seu carregamento comparado com a renderização inicial do HTML, então renderizar uma UI diferente imediatamente após hidratação pode também ser prejudicial para o usuário.


Atualizando um componente raiz hidratado

Após a finalização da hidratação da raiz, você pode chamar root.render para atualizar o componente raiz do React. Diferente de createRoot, você não precisa frequentemente fazer isso porque o conteúdo inicial já renderizou como HTML.

Se você chamar root.render em algum ponto após a hidratação, e a estrutura do componente árvore coincidir com o que foi previamente renderizado, o React preservará o state. Note como você pode escrever no campo de texto, o que significa que a atualização de repetidos render chamados cada segundo nesse exemplo não são destrutivos:

import { hydrateRoot } from 'react-dom/client';
import './styles.css';
import App from './App.js';

const root = hydrateRoot(
  document.getElementById('root'),
  <App counter={0} />
);

let i = 0;
setInterval(() => {
  root.render(<App counter={i} />);
  i++;
}, 1000);

É incomum chamar root.render para uma raiz hidratada. Ao invés disso, usualmente, você atualizará o state dentro de um dos componentes.

Mostrando diálogo para erros não interceptados

Canary

onUncaughtError só está disponível para o último release do React Canary.

Por padrão, o React imprimirá no console todos os log’s de erros não interceptados. Para implementar seu prórpio relatório de erros, você pode definir o método opcional onUncaughtError da raiz:

import { hydrateRoot } from 'react-dom/client';

const root = hydrateRoot(
document.getElementById('root'),
<App />,
{
onUncaughtError: (error, errorInfo) => {
console.error(
'Uncaught error',
error,
errorInfo.componentStack
);
}
}
);
root.render(<App />);

O método onUncaughtError é uma função que é chamada com dois argumentos:

  1. O error que é lançado.
  2. Um objeto errorInfo que contém o componentStack do erro.

Você pode usar o método onUncaughtError da raiz para exibir diálogos de erros:

import { hydrateRoot } from "react-dom/client";
import App from "./App.js";
import {reportUncaughtError} from "./reportError";
import "./styles.css";
import {renderToString} from 'react-dom/server';

const container = document.getElementById("root");
const root = hydrateRoot(container, <App />, {
  onUncaughtError: (error, errorInfo) => {
    if (error.message !== 'Known error') {
      reportUncaughtError({
        error,
        componentStack: errorInfo.componentStack
      });
    }
  }
});

Mostrando erros de Error Boundary

Canary

onCaughtError só está disponível para o último release do React Canary.

Por padrão, React impriirá todos os log’s de erros interceptados por um Error Boundary no console.error. Para mudar esse comportmento, você pode definir o método opcional onCaughtError da raiz para erros interceptados por Error Boundary:

import { hydrateRoot } from 'react-dom/client';

const root = hydrateRoot(
document.getElementById('root'),
<App />,
{
onCaughtError: (error, errorInfo) => {
console.error(
'Caught error',
error,
errorInfo.componentStack
);
}
}
);
root.render(<App />);

O método onCaughtError é uma função que possui dois argumentos:

  1. O error que foi interceptado pelo boundary.
  2. Um objeto errorInfo que contém o componentStack do erro.

Você pode usar o método onCaughtError da raiz para mostrar diálogos de erro ou filtrar erros conhecidos do log:

import { hydrateRoot } from "react-dom/client";
import App from "./App.js";
import {reportCaughtError} from "./reportError";
import "./styles.css";

const container = document.getElementById("root");
const root = hydrateRoot(container, <App />, {
  onCaughtError: (error, errorInfo) => {
    if (error.message !== 'Known error') {
      reportCaughtError({
        error,
        componentStack: errorInfo.componentStack
      });
    }
  }
});

Mostrando um diálogo para erros recuperáveis de diferença de hidratação

Quando o React encontra uma diferença de hidratação, ele automaticamente tentará recuperar renderizando no cliente. Por padrão, o React imprimirá o log de erros de diferença de hidratação no console.error. Para mudar esse comportamento, você pode definir o método opcional onRecoverableError da raiz:

import { hydrateRoot } from 'react-dom/client';

const root = hydrateRoot(
document.getElementById('root'),
<App />,
{
onRecoverableError: (error, errorInfo) => {
console.error(
'Caught error',
error,
error.cause,
errorInfo.componentStack
);
}
}
);

O método onRecoverableError é uma função com dois argumentos:

  1. O error lançado pelo React. Alguns erros podem incluir a causa original como error.cause.
  2. Um objeto errorInfo que contém o componentStack do erro.

Você pode usar o método onRecoverableError da raiz para mostrar diálogos de erro para diferenças de hidratação:

import { hydrateRoot } from "react-dom/client";
import App from "./App.js";
import {reportRecoverableError} from "./reportError";
import "./styles.css";

const container = document.getElementById("root");
const root = hydrateRoot(container, <App />, {
  onRecoverableError: (error, errorInfo) => {
    reportRecoverableError({
      error,
      cause: error.cause,
      componentStack: errorInfo.componentStack
    });
  }
});

Solução de problemas

Estou recebendo esse erro: “You passed a second argument to root.render”

Um erro comum é passar as opções de hydrateRoot para root.render(...):

Console
Warning: You passed a second argument to root.render(…) but it only accepts one argument.

Para correção, passe as opções da raiz para hydrateRoot(...), e não para root.render(...):

// 🚩 Wrong: root.render only takes one argument.
root.render(App, {onUncaughtError});

// ✅ Correct: pass options to createRoot.
const root = hydrateRoot(container, <App />, {onUncaughtError});