-
I have two errors only when the app is deployed, not in dev mode nor the local build, which is another issue altogether. 1st error: Text content does not match server-rendered HTML. 2rd error: There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering. I found out that the errors are caused by the formatting of the time that I get from an API in Unix timestamp format (current.dt), but I don't know how else to handle this. Should I format the date on the server i.e. in getServerSideProps? Or are these errors caused by something else? Live app shows the erros in the console: https://weather-test-kz4cjca1o-karelfh.vercel.app/ WeatherDisplay: import type { Current, Location } from '../../types/typeWeatherApi';
const WeatherDisplay = ({
location,
current,
}: {
location: Location;
current: Current;
}) => {
// This causes the errors:
const hour = ('0' + new Date(current.dt * 1000).getHours()).slice(-2);
const minutes = ('0' + new Date(current.dt * 1000).getMinutes()).slice(-2);
const currentTime = `${hour}:${minutes}`;
return (
<article>
<h1>{location.name}</h1>
<p>{location.country}</p>
<p>{current.feels_like}</p>
{/* This causes the error: */}
<p>{currentTime}</p>
</article>
);
};
export default WeatherDisplay; Index page with getServerSideProps: import axios from 'axios';
import { useState } from 'react';
import type { NextPage, GetServerSideProps } from 'next';
import type { Data, Location } from '../types/typeWeatherApi';
import { getCurrentWeather } from './api/currentWeather';
import { getCurrentLocation } from './api/currentLocation';
import WeatherDisplay from '../components/weather-display/weather-display';
const Home: NextPage = ({ initialWeather, initialLocation }: any) => {
const [location, setLocation] = useState<Location>(initialLocation);
const [weatherData, setWeatherData] = useState<Data>(initialWeather);
const [units, setUnits] = useState('metric');
const [lang, setLang] = useState('en');
const getGeolocationData = () => {
navigator.geolocation.getCurrentPosition(
(position) => {
axios
.get(
`/api/currentLocation?lon=${position.coords.longitude}&lat=${position.coords.latitude}`
)
.then((response) => setLocation(response.data[0]));
},
(error) => {
console.warn(`ERROR(${error.code}): ${error.message}`);
},
{
timeout: 10000,
maximumAge: 0,
}
);
};
const getCurrentWeather = async () => {
await axios
.get(
`/api/currentWeather?lon=${location.lon}&lat=${location.lat}&units=${units}&lang=${lang}`
)
.then((response) => setWeatherData(response.data))
.catch((error) => console.error(error));
};
return (
<>
<section>
<div>
<p>Latitude: {location.lat}</p>
<p>Longitude: {location.lon}</p>
</div>
<button onClick={getGeolocationData}>Get Current Location</button>
<button onClick={getCurrentWeather}>Get Current Weather</button>
</section>
<section className="current-weather">
<WeatherDisplay location={location} current={weatherData.current} />
</section>
</>
);
};
export const getServerSideProps: GetServerSideProps = async () => {
const defaultWeatherQuery = {
lat: '51.5072',
lon: '0.1276',
exclude: '',
units: 'metric',
lang: 'en',
};
const defaultLocationQuery = {
lat: defaultWeatherQuery.lat,
lon: defaultWeatherQuery.lon,
};
const defaultWeather = await getCurrentWeather(defaultWeatherQuery);
const defaultLocation = await getCurrentLocation(defaultLocationQuery);
return {
props: {
initialWeather: defaultWeather,
initialLocation: defaultLocation[0],
},
};
};
export default Home; |
Beta Was this translation helpful? Give feedback.
Replies: 6 suggested answers 14 replies
{{editor}}'s edit
{{editor}}'s edit
-
Hi, Yeah so this two errors are combined. Because the client, on the first frame, needs to see the same HTML as the server sent over, in order to place event listeners and place siblings and children correctly, if there's an error while this is being done, React logs an error. This has happened all the way back to React 17 AFAIK. Problem number 2 kicks in with the new rendering root, which sees this as a rendering error, which is unfriendly to concurrent features, so hydration fails and it throws the entire thing out the window. At least that's how I interpret the second error. Lots of people just ignored error number 1, during the entire 2 years React 17 was out, not saying you did, but many use libraries that did, and others just ignored them. Possible work aroundTime and randomness are two of the things that most commonly produce this. I would attack this problem like this:
import { CSSProperties, useEffect, useState } from "react";
import { Example } from "../components/Example";
const TimeDisplay = ({ time }: { time: number }) => {
const [currentTime, setCurrentTime] = useState(() => {
const hour = ("0" + new Date(time * 1000).getUTCHours()).slice(-2);
const minutes = ("0" + new Date(time * 1000).getUTCMinutes()).slice(-2);
return `${hour}:${minutes} UTC`;
});
useEffect(() => {
setCurrentTime(() => {
const hour = ("0" + new Date(time * 1000).getHours()).slice(-2);
const minutes = ("0" + new Date(time * 1000).getMinutes()).slice(-2);
return `${hour}:${minutes}`;
});
}, [time]);
// optionally make this content take space, but remain invisible, to avoid layout shifts
// it's better to use a CSS class instead
const style: CSSProperties = {
visibility: currentTime.includes("UTC") ? "hidden" : "visible",
};
return (
<article>
<p style={style}>{currentTime}</p>
</article>
);
}; I'd recommend also mixing in the DateTimeFormat API, https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat, which can take care of showing the time zone for you: const date = new Date(Date.UTC(2012, 11, 20, 3, 0, 0, 200));
options = {
hour: 'numeric', minute: 'numeric', second: 'numeric',
timeZoneName: 'short'
};
console.log(new Intl.DateTimeFormat('sv-SE', options).format(date)); If I run the above on a repl, I get
Since users arrive to you from different time zones, you have to account for a frame where you show UTC, and then you show the time for the users time zone. Combining this with CSS visibility is good, because you can avoid layout shifts and such, the time will take its place, but it won't be visible. Robots, crawlers still see the time, and for users with JS disabled, you could add a |
Beta Was this translation helpful? Give feedback.
-
The solution @icyJoseph suggested works, but does have a drawback. The So, for example, if your user is navigating to multiple different screens that contain the You can avoid these repeated double-renders by using a Context Provider to store the hydration state. Basically you just move the If you want to know more, I wrote about this in a blog post: The code would look something like this: const HydrationContext = React.createContext(false);
function HydrationProvider({ children }) {
const [hydrated, setHydrated] = React.useState(false);
React.useEffect(() => {
setHydrated(true);
}, []);
return <HydrationContext.Provider value={hydrated}>{children}</HydrationContext.Provider>;
}
function MyDateComponent({ theDate }) {
// Retrieve the hydration state from the context
const hydrated = React.useContext(HydrationContext);
const date = new Date(theDate);
const formatted_date = hydrated ? date.toLocaleDateString() : date.toUTCString();
return <div>{formatted_date}</div>;
}
export default function OnlyRerenderAfterHydration() {
return (
<HydrationProvider>
<MyDateComponent />
</HydrationProvider>
);
} I also published an NPM package that makes it easier to render different content on the client and server: react-hydration-provider If you were to use it with the code you provided, it would look something like this: import React from "react";
import { HydrationProvider, Server, Client } from "react-hydration-provider";
import type { Current, Location } from "../../types/typeWeatherApi";
const WeatherDisplay = ({
location,
current,
}: {
location: Location,
current: Current,
}) => {
const date = new Date(current.dt * 1000);
return (
<article>
<h1>{location.name}</h1>
<p>{location.country}</p>
<p>{current.feels_like}</p>
<Server>
<p style={{ visibility: "hidden" }}>{date.toUTCString()}</p>
</Server>
<Client>
<p>
{date.toLocaleTimeString(undefined, {
hour: "numeric",
hourCycle: "h24",
minute: "numeric",
})}
</p>
</Client>
</article>
);
};
function App() {
return (
<HydrationProvider>
<WeatherDisplay
location={{
name: "LocationName",
country: "CountryName",
feels_like: "27°C",
}}
current={{
dt: new Date().getTime() / 1000,
}}
/>
</HydrationProvider>
);
} In a lot of simple cases it's probably easier to just use |
Beta Was this translation helpful? Give feedback.
{{editor}}'s edit
{{editor}}'s edit
-
The following should work, no? Throws hydration errors for me:
import { useRef, useState, useEffect } from "react";
function MyApp({ Component, pageProps }) {
const [currentTime, setCurrentTime] = useState(() => {
const hour = ("0" + new Date().getUTCHours()).slice(-2);
const minutes = ("0" + new Date().getUTCMinutes()).slice(-2);
const seconds = ("0" + new Date().getUTCSeconds()).slice(-2);
return `${hour}:${minutes}:${seconds}`;
});
// useEffect(() => {
// const interval = setInterval(() => {
// setCurrentTime(() => {
// const hour = ("0" + new Date().getHours()).slice(-2);
// const minutes = ("0" + new Date().getMinutes()).slice(-2);
// const seconds = ("0" + new Date().getSeconds()).slice(-2);
// return `${hour}:${minutes}:${seconds}`;
// });
// }, 1000);
// return () => clearInterval(interval);
// }, []);
return (
<span>
{currentTime}
</span>
);
}
export default MyApp; |
Beta Was this translation helpful? Give feedback.
{{editor}}'s edit
{{editor}}'s edit
-
No one said about but Cloudflare replaced double spaces with a space when you enable Auto Minify (HTML) in optimization. |
Beta Was this translation helpful? Give feedback.
-
Can this problem be solved with https://reactjs.org/docs/dom-elements.html#suppresshydrationwarning |
Beta Was this translation helpful? Give feedback.
-
import { useEffect, useState } from "react"
/**
* @description Render a date SSR / CSR
*/
function IsomorphicDate(props: { time: number }) {
const [date, setDate] = useState(props.time)
useEffect(() => {
setDate(new Date(props.time).toLocaleString())
}, []);
return date;
}
export default IsomorphicDate I created this component to fix the issue for dates. |
Beta Was this translation helpful? Give feedback.
Hi,
Yeah so this two errors are combined.
Because the client, on the first frame, needs to see the same HTML as the server sent over, in order to place event listeners and place siblings and children correctly, if there's an error while this is being done, React logs an error. This has happened all the way back to React 17 AFAIK.
Problem number 2 kicks in with the new rendering root, which sees this as a rendering error, which is unfriendly to concurrent features, so hydration fails and it throws the entire thing out the window.
At least that's how I interpret the second error. Lots of people just ignored error number 1, during the entire 2 years React 17 was out, not saying you did, but …