Reputation: 111
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
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
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
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
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.
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:
teste
, which generates a random array of numbers and renders it.teste
, which generates a NEW random array of numbers and renders it.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.
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