Gabriel-vieira
Gabriel-vieira

Reputation: 111

Error: Text content does not match server-rendered HTML

I'm trying to perform a simple algorithm in next.js and I'm getting this hydration errors.

This is the code I'm using:

import numeros from "../../functions/numberGenerators.js"

export default function teste(){
    let number = numeros()
    return number.map(n =>  
    <div key={n}>
        Number: {n}
    </div>)
}

And:

export default function megaSena(qtde = 6){
    let listNumbers = []
    while(listNumbers.length <= qtde - 1){
        const numeroRandom = parseInt(Math.random() * 60) + 1
        if (!listNumbers.includes(numeroRandom)){
            listNumbers.push(numeroRandom)
        }
    }
    return listNumbers
}

And I'm having these following errors:

1° - Error: Hydration failed because the initial UI does not match what was rendered on the server.

2° - Error: Text content does not match server-rendered HTML.

3° - Error: Hydration failed because the initial UI does not match what was rendered on the server.

4° - Error: There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.

What can I do to resolve this?

Upvotes: 11

Views: 30502

Answers (4)

GeM
GeM

Reputation: 97

Next.js Simple ways of Solutions a lot of Problems like this represented in nextjs.org.
Maybe this can help you to solve your problem: https://nextjs.org/docs/messages/react-hydration-error

Upvotes: -1

mjosh
mjosh

Reputation: 142

Just as @milad-amirvafa said,

Comment out some part of your code to get the element that causes hydration error, then add a state hook with false as initial state, like so, const [hydrated, setHydrated] = useState(false);

then set that state to true in a useEffect hook, add the hydrated state to the component which causes error. Your code should look like the below:

const Component = () => {
const [hydrated, setHydrated] = useState(false);
useEffect(() => {
    setHydrated(true);
},[])
return (
    <>
        {hydrated && <div>The element which throws error</div>}
    </>
)} 
export default Component;

Upvotes: 9

Milad Amirvafa
Milad Amirvafa

Reputation: 51

Follow the solution below to solve the problem. This makes the component not to be rendered in first time.

const Component = () => {
    const [showComponent, setShowComponent] = useState(false);
    useEffect(() => {
        setShowComponent(true);
    },[])
    return (
        <>
            showComponent && <div>The component which throws error</div>
        </>
    )
}

export default Component

Upvotes: 0

twiz
twiz

Reputation: 10588

These error messages all essentially say the same thing:

The HTML from the server does not match what is rendered by your app.

Why is this happening?

When teste is called, it does not always return the same thing. It is designed to display random numbers.

This means it will display a different list of numbers, every time teste is rendered.

When using Server-Side Rendering (SSR) with a framework like NextJS, it is using React hydration. This is just a fancy word for saying that React is figuring out how to take the DOM structure rendered from the HTML file and convert it into a running single-page app (SPA).

For this to work correctly, React Hydration requires that the HTML from the server and what your client-side app renders are an EXACT match.

So in your scenario, this is what is happening:

  1. The server needs to generate the HTML file.
  2. It calls teste, which generates a random array of numbers and renders it.
  3. The server sends this HTML to the client.
  4. The client app calls teste, which generates a NEW random array of numbers and renders it.
  5. It attempts to "hydrate" the app, but the HTML and client-render do not match.
  6. NextJS/React doesn't know how to handle this mismatch, so it logs the errors you saw.

How do you fix this?

The fix is simple, but it requires you to be mindful of when it is or isn't necessary.

The common approach to this is to utilize useEffect() to ensure both the server and client render the same thing during the hydration process, and only render the dynamic content on the client afterwords.

The easiest way to do this is to simply render nothing. Using your code, that would look something like this:

import React from "react";

export default function Teste() {
    const [hydrated, setHydrated] = React.useState(false);
    React.useEffect(() => {
        setHydrated(true);
    }, []);
    if (!hydrated) {
        // Returns null on first render, so the client and server match
        return null;
    }

    let number = numeros();
    return number.map((n) => <div key={n}>Number: {n}</div>);
}

What this is doing is ensuring that the first time Teste renders it will return null.

This first render is what the server uses to generate the HTML file and is also what the client-side app will use for the "hydration" process.

During this first run, hydrated will have the default value of false, which will cause the component to return null. Also in this first run, useEffect() will call setHydrated(true), which will trigger a second render after the first has completed.

By the time the second render runs, the app will already be hydrated, so there is no need to worry about the errors occurring anymore. At this point hydrated will be true, so the randomized numbers will render normally.

More info

If you want to learn more about React hydration I wrote a blog post about fixing these types of errors.

I also published an NPM package that helps simplify dealing with these types of hydration errors: react-hydration-provider

To fix your errors using react-hydration-provider, your code would look something like this:

import { HydrationProvider, Client } from "react-hydration-provider";

function App() {
    return (
        <HydrationProvider>
            <Client>
                <Teste />
            </Client>
        </HydrationProvider>
    );
}

function Teste() {
    let number = numeros();
    return number.map((n) => <div key={n}>Number: {n}</div>);
}

This would make Teste only render on the client-side once the app has hydrated.

You could also do something a little more complex like this:

import { HydrationProvider, Server, Client } from "react-hydration-provider";

function App() {
    return (
        <HydrationProvider>
            <Teste />
        </HydrationProvider>
    );
}

function Teste() {
    let number = numeros();
    return number.map((n) => (
        <div key={n}>
            <span>Number: </span>
            <Client>{n}</Client>
            <Server>Loading...</Server>
        </div>
    ));
}

This would allow your app to initially render a loading message for each of the numbers and then replace it with the random numbers once the app completes the hydration process.

Upvotes: 24

Related Questions