I have a common problem:
My header should show "register" for guests or "profile" for users.
The header is in the root layout file, the user state is saved in a cookie.
In a client component, I am reading the cookie and in useEffect change the status. A simplified version will be this:
'use client';
export default function NavRegisterOrProfile() {
const [user, setUser] = useState(false);
useEffect(() => {
const user = getUserCookie();
if (user) {
setUser(user);
}
}, []);
return (
<>
{user ? <div>Profile</div> : <div>Register</div> }
</>
Problem: This creates a hydration error for users (for guests I am not getting an error):
Error: Hydration failed because the initial UI does not match what was rendered on the server
Undesired solution: I can read the cookie on the server side using
import { cookies } from 'next/headers';
export default function Page() {
const nextCookies = cookies();
const theme = nextCookies.get('user');
return '...'
}
But then my entire site (since this is in the root layout) cannot be static site generated (SSG) and moves to SSR. So this is a big performance issue.
Another undesired solution:
I can show nothing until the component is mounted, but for SEO reasons I don't want the UI to shift creating a CLS issue. I want the guest version to be the default SSG, and after the component mounts - change to the user version without the hydration issue.
Full actual code:
'use client';
import React, { Suspense, useEffect, useState } from 'react';
import { hookstate, useHookstate } from '#hookstate/core';
import { getUserCookie } from 'helpers/cookieHelper';
import RegisterModal from '../RegisterModal';
import Link from 'next/link';
import UserAvatar from '#shared/user/UserAvatar';
import Image from 'next/image';
export const globalUserState = hookstate(false);
export default function NavRegisterOrProfile() {
const [showRegister, setShowRegister] = useState(false);
const [hasMounted, setHasMounted] = React.useState(false);
const userState = useHookstate(globalUserState);
useEffect(() => {
setHasMounted(true);
const user = getUserCookie(true);
if (user) {
userState.set(() => user);
}
}, []);
const user = userState.get();
return (
<>
{hasMounted && user?.name ? (
<>
<Link href="/profile" id="desktop-nav-profile">
<UserAvatar user={user} />
</Link>
{user?.isAdmin && (
<div className="shadow-md h-12 w-12 ml-4 flex justify-center items-center rounded-full">
<Link href="/admin">
<Image
src="/images/heart.svg"
width={30}
height={26}
alt="admin"
priority
/>
</Link>
</div>
)}
</>
) : (
<>
<strong
className="mx-3 px-1 cursor-pointer hover:underline"
data-cy="header-register"
id="desktop-nav-register"
onClick={() => setShowRegister(true)}
>
Join Us
</strong>
{showRegister && (
<Suspense>
<RegisterModal
closeModalCallback={() => {
setShowRegister(false);
}}
/>
</Suspense>
)}
</>
)}
</>
);
}
So I found an ugly solution, I really don't want to use it, but it makes the annoying error go away...
Adding timeout on the useEffect "solves" this since the initial render matches the server DOM.
useEffect(() => {
setHasMounted(true);
const user = getUserCookie(true);
if (user) {
setTimeout(() => {
userState.set(() => user);
}, 100);
}
}, []);
Let's agree as a community that this trash can't be the accepted answer here. Anyone has a better solution?
I'm trying to get SSR working in my app but I get the error:
Hydration failed because the initial UI does not match what was
rendered on the server.
Live demo code is here
Live demo of problem is here (open dev tools console to see the errors):
// App.js
import React from "react";
class App extends React.Component {
head() {
return (
<head>
<meta charSet="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no"
/>
<meta name="theme-color" content="#000000" />
<title>React App</title>
</head>
);
}
body() {
return (
<body>
<div className="App">
<h1>Client says Hello World</h1>
</div>
</body>
);
}
render() {
return (
<React.Fragment>
{this.head()}
{this.body()}
</React.Fragment>
)
}
}
export default App;
// index.js
import React from "react";
import * as ReactDOM from "react-dom/client";
import { StrictMode } from "react";
import App from "./App";
// const container = document.getElementById("root");
const container = document.getElementsByTagName("html")[0]
ReactDOM.hydrateRoot(
container,
<StrictMode>
<App />
</StrictMode>
);
The Html template shown in the live demo is served by the backend and generated using the following code:
const ReactDOMServer = require('react-dom/server');
const clientHtml = ReactDOMServer.renderToString(
<StrictMode>
<App />
</StrictMode>
)
// serve clientHtml to client
I need to dynamically generate <head></head> and <body></body> section as shown in the App class
I have been experiencing the same problem lately with NextJS and i am not sure if my observations are applicable to other libraries. I had been wrapping my components with an improper tag that is, NextJS is not comfortable having a p tag wrapping your divs, sections etc so it will yell "Hydration failed because the initial UI does not match what was rendered on the server". So I solved this problem by examining how my elements were wrapping each other. With material UI you would need to be cautious for example if you use a Typography component as a wrapper, the default value of the component prop is "p" so you will experience the error if you don't change the component value to something semantic. So in my own opinion based on my personal experience the problem is caused by improper arrangement of html elements and to solve the problem in the context of NextJS one will have to reevaluate how they are arranging their html element.
import Image from 'next/image'
/**
* This might give that error
*/
export const IncorrectComponent = ()=>{
return(
<p>
<div>This is not correct and should never be done because the p tag has been abused</div>
<Image src='/vercel.svg' alt='' width='30' height='30'/>
</p>
)
}
/**
* This will work
*/
export const CorrectComponent = ()=>{
return(
<div>
<div>This is correct and should work because a div is really good for this task.</div>
<Image src='/vercel.svg' alt='' width='30' height='30'/>
</div>
)
}
Importing and running some of the packages can cause this error too. For example, when I used Swiper.js package I encountered this problem. It's mostly because the package is using Window object somewhere.
Since it isn't a good practice to modify the content of the package itself, the best way to tackle such issues is to render the component only after the DOM is loaded. So you can try this.
const Index = () => {
const [domLoaded, setDomLoaded] = useState(false);
useEffect(() => {
setDomLoaded(true);
}, []);
return (
<>
{domLoaded && (
<Swiper>
<div>Test</div>
</Swiper>
)}
</>
);
};
export default Index;
This make the app client-side render, it's work for me:
export default function MyApp({ Component, pageProps }: AppProps) {
const [showChild, setShowChild] = useState(false);
useEffect(() => {
setShowChild(true);
}, []);
if (!showChild) {
return null;
}
if (typeof window === 'undefined') {
return <></>;
} else {
return (
<Provider store={store}>
<Component {...pageProps} />
</Provider>
);
}
}
I also using NextJS, Redux Toolkit
If you're using a table, you probably missed <tbody>
incorrect:
<table>
<tr>
<td>a</td>
<td>b</td>
</tr>
</table>
correct:
<table>
<tbody>
<tr>
<td>a</td>
<td>b</td>
</tr>
</tbody>
</table>
Removing the <p> tag solved my similar problem with NEXTJS..
The error is misleading as it's not about hydration but wrongly nested tags. I figured out the conflicting tags by removing html and reloading the page until I found the problem (binary search).
In my case it was caused by <a> within <Link>. I thought next.js requires the <a> within the Link but obviously that's not/no longer the case.
See next.js documentation about <Link> tag
I have react 18.2.0 with next 12.2.4 and I fix hydration with this code
import { useEffect, useState } from 'react'
import { Breakpoint, BreakpointProvider } from 'react-socks';
import '../styles/globals.scss'
function MyApp({ Component, pageProps }) {
const [showChild, setShowChild] = useState(false)
useEffect(() => {
setShowChild(true)
}, [])
if (!showChild) {
return null
}
return (
<BreakpointProvider>
<Component {...pageProps} />
</BreakpointProvider>
)
}
export default MyApp
this issue comes in nextjs because dangerouslySetInnerHTML support only div tag. if you insert with other tag its not work.
<div dangerouslySetInnerHTML={{__html:data.description}}></div>
So let me explain why this error can occur in your NEXT JS application.
There are some tags that you can't use inside another tag.
For example you can't use div inside an anchor td tag. You can't use p tag inside a span. Same goes for other tags like table, ul etc.
You need to go to your code and remove all the invalid tags used.
In my case, I used a a tag inside span which in invalid.
This is invalid
<span>
<a target="_blank" href="https://askhumans.io"> Login </a>
</span>
This is valid.
<a target="_blank" href="https://askhumans.io"> <span> Login </span> </a>
it will work:
function MyApp({ Component, pageProps }) {
const [showing, setShowing] = useState(false);
useEffect(() => {
setShowing(true);
}, []);
if (!showing) {
return null;
}
if (typeof window === 'undefined') {
return <></>;
} else {
return (
<RecoilRoot>
<MainLayout>
<Component {...pageProps} />
</MainLayout>
</RecoilRoot>
);
}
}
export default MyApp;
here I used recoil for state managing.
If you're using NextJS and Material UI with emotion as the styling engine, then you may need to check the semantics of your components. You can find hints in the errors logged to the browser console.
Example: adding Box component inside Iconbutton will cause an error
There's no need to create a custom NextJS _document.js file because #emotion/react version 10 and above works with NextJS by default.
If u use html tags u want to place them in correct way and correct order. in NEXTJS.
ex: If u use table.
-> U must add the tbody tag
<!-- Wrong -->
<table>
<tr>
<td>Element</td>
<td>Element</td>
</tr>
</table>
<!-- Correct Way -->
<table>
<tbody> <!-- This is a Must -->
<tr>
<td>Element</td>
<td>Element</td>
</tr>
</tbody>
</table>
in Next v13 and upper you shouldn't use <a> as child inside <Link>.
if you use that, you'll get error.
in this case I use that in another way:
const Child = () => <a>hello my friend</a>
const Parent = () => {
return (
<Link href="/">
<Child />
</Link>
)
}
here I got this error, and I changed child structure for remove <a> to resolve it.
So mine is a NEXT JS app.
I am using the react-use-cart module and it seems it has issues with react #18.0.0.
I am not sure how this is possible but downgrading to react #17.0.2 removed my errors.
Previously I was using react #18.0.0
I simply ran npm uninstall react react-dom and installed versions #17.0.2.
Wahala, everything now works as expected.
I had the same issue when tried to put a div inside Card Text of React-Bootstrap.
The error can be reproduced by:
import type { NextPage } from 'next'
import { Card } from 'react-bootstrap';
...
const testPage : NextPage = () => {
...
return (
...
<Card.Text>
<div>It's an error</div>
</Card.Text>
...
)}
export default testPage
To fix it, i just removed the html tag.
I think that some react components doesn't accept html tags inside.
Make sure to wrap in Suspense the lazy modules you import.
In my case I imported
const Footer = React.lazy(() => import('../Footer/Index'));
but I was using it just like a normal module
<Footer />
I wrapped it in Suspense and the error was gone.
<Suspense fallback={<div>Loading...</div>}>
<Footer />
</Suspense>
Bottom line
If this error is given to you on the home page, try to comment some of the components you use until you find where the error is coming from.
In my case, it's an user error in nested list. I forgot adding a ul in li so it was just nested lis.
Make sure you dont have next/Link nested, I needed to refactor the code and forgod that I had a next/Link before wrapping the image.
for example
<CompanyCardStyle className={className}>
//Open Link
<Link href={route('companyDetail', { slug: company.slug })}>
<a className='d-flex align-items-center'>
<div className='company-card'>
<div className='d-flex align-items-center col-name-logo'>
<div className='company-logo'>
//Remove link and let the <a> child
<Link href={route('companyDetail', { slug: company.slug })}>
<a><img src={company.logoUrl} width={'100%'} /></a>
</Link>
</div>
<h6 className='mb-0'>{company.name}</h6>
</div>
.....
</div>
</a>
</Link>
</CompanyCardStyle>
I had this issue when I moved the pages directory in NextJs. I solved it by deleting the .next folder and rebuilding it using yarn build.
I solved this problem by NextJs dynamic import with ssr: false
import dynamic from 'next/dynamic'
import { Suspense } from 'react'
const DynamicHeader = dynamic(() => import('../components/header'), {
ssr: false,
})
export default function Home() {
return (
<DynamicHeader />
)
}
just go to browser, chrome->three bars button on top right corner->more tools->clear browsing history-> delete cookies.
no more error
i ran this piece of code and the problem went away
import "#/styles/globals.css";
import { useEffect, useState } from "react";
export default function App({ Component, pageProps }) {
const [showChild, setShowChild] = useState(false);
useEffect(() => {
setShowChild(true);
}, []);
if (!showChild) {
return null;
}
if (typeof window === "undefined") {
return <></>;
} else {
return <Component {...pageProps} />;
}
}
I had the same issue with Next.js and Faker.js, and i just use conditional rendering and it's solved. I think it happened because the values from faker.js changes twice when page first loading. Code below may help you.
`
export default function Story() {
const [avatar, setAvatar] = useState(null);
const [name, setName] = useState(null);
useEffect(() => {
setAvatar(faker.image.avatar());
setName(faker.name.findName());
}, []);
return (
<>
{avatar && name && (
<div>
<Image
src={avatar}
alt="Story Profile Picture"
width={50}
height={50}
/>
<p>{name}</p>
</div>
)}
</>
);
}
`
You Can wrap your component that cause this error with Nossr from mui(material-ui)
import NoSsr from "#mui/material/NoSsr";
<NoSsr> {your contents} </NoSsr>
to get more info: https://mui.com/material-ui/react-no-ssr/
In my case, when I used reverse() function before mapping my list, I was getting a similar error.
In my case, neither making the HTML more semantic nor modifying the _app.tsx to check if the window was loaded.
The only solution, for now, was to downgrade the React version and the React DOM.
I'm using NextJS.
I switched to version 17.0.2.
Hydration failed because the initial UI does not match what was rendered on the server
You should check the console for errors like:
Warning: Expected server HTML to contain a matching <div> in <div>.
and fix them.
Copied from https://github.com/vercel/next.js/discussions/35773
In my case, I faced this problem because I had used <img /> tag instead of Next.js <Image /> tag because I couldn't use tailwind classes with the <Image /> tag. Replacing the <img /> tag with Next.js <Image /> tag solved the issue for me. Then I wrapped the <Image /> tag with a div tag and added the classes to that.
I ran into same issue using NextJS with Material UI.
Actual solution is to use NextJS dynamic import.
In the example below do not import header component as:
import Header from "../components/header"
Instead do:
import dynamic from 'next/dynamic'
const DynamicHeader = dynamic(() => import('../components/header'), {
suspense: true,
})
Check the NextJS documentation below:
https://nextjs.org/docs/advanced-features/dynamic-import
I had this issue even with create-next-app without any changes and tried with different browser and different profiles. I see that there is no problem with them so I investigate my extensions but it didn't solve the problem. Finally I've solved it with investigating chrome dev tools settings. My problem related with "Enable Local Overrides". So I've unchecked that and it solved.
Enable Local Overrides on Chrome Settings
How can I update my index the first time I render the page, everything is working as I want but the first time it renders it returns the undefined so I can't get the info at the first time rendering.
First-time refresh/ render the page:
After swipe or click next and then return to the previous item, it'll update correctly.
Here's my code:
import { useState, useEffect } from "react"
import './home.css'
import { supabase } from '../../supabaseClient'
import { Swiper, SwiperSlide } from 'swiper/react'
import SwiperCore, { Pagination,Navigation } from 'swiper';
import 'swiper/css'
SwiperCore.use([Pagination,Navigation]);
function Home(){
const [animeDetail, setAnimeDetail] = useState([])
const [swiperIndex, setSwiperIndex] = useState()
useEffect(async ()=>{
fetchAnime()
}, [])
async function fetchAnime() {
const { data } = await supabase
.from ('anime')
.select ()
setAnimeDetail(data)
}
return (
<>
<div className="spacer">
</div>
<div className="home-section">
<h2>Home Page</h2>
<Swiper
runCallbacksOnInit = {true}
centeredSlides={true}
slidesPerView = {7}
spaceBetween={10}
loop={false}
pagination={false}
// I set my index to the state here
onActiveIndexChange={(swiperCore) => {setSwiperIndex(swiperCore.realIndex)}}
navigation={true} className="mySwiper">
{animeDetail.map((element, i)=>
(
<SwiperSlide key = {i}>
<img src={element.anime_image}/>
</SwiperSlide>
)
)}
</Swiper>
{/* Current index of active slide */}
{console.log(swiperIndex)}
{/* The specific info contains on active slide */}
{console.log(animeDetail[swiperIndex])}
{/* An array list contains each anime */}
{console.log(animeDetail)}
</div>
</>
)
}
export default Home
Edit: After I tried #Dae Hyeon Mun's solution, the problem is still there.
I still get the undefined when the first time renders/refresh the page.
You should start with 0 as the initial state value for the index:
// const [swiperIndex, setSwiperIndex] = useState() // before
const [swiperIndex, setSwiperIndex] = useState(0) // after
On the first render, you will only have the data that you provide as the initial values to your state (empty array and 0). useEffect doesn't run until after the first render.
Try fix code as below
useEffect(async () => {
const { data } = await supabase
.from ('anime')
.select()
setAnimeDetail(data)
}, [])
if (animeDetail.length === 0) {
return <div>Loading</div>;
}
.... rendering //
I have this code on Codesandbox the goal is to be able to pass 5 Divs, on load using use Effect.
and a second option to add a div on click when if the user feels like it. the code is partially working, but it has a anti-patter issue which is putting the component in the state instead of changing the state using map to pass the changes..
please take a look I would like to hear your opinion on this, what I do understand is importing the Div element like this could affect performance, I want to avoid bad practice as much as possible.
import React, { useEffect, useState } from "react";
import Div from "./Div";
import "./styles.css";
import { v4 as uuidv4 } from "uuid";
export default function App() {
useEffect(() => {
// on start add 5 divs in to the local state Array on the frist load
});
const [div, setDiv] = useState([]);
const addDiv = () => {
// add an extra div on click if needed with id using the right pattern
setDiv([...div, <Div id={uuidv4()} />]);
};
return (
<div className="App">
{div}
<button onClick={addDiv} type="button">
Click Me!
</button>
</div>
);
}
//Dev dependencise
"uuidv4": "6.2.12"
Codesandbox
Putting JSX elements into state is a bad idea because they won't be reactive - you won't be able to (reliably) pass down state, state setters, and other useful things as props.
It's not so much a performance issue as a code maintainability issue - if you add additional functionality to your Div component and to your App you may find that your current approach won't work due to stale values that the JSX elements in state close over.
If you need the ability to delete a value, use the index of the div in the array and pass it down as needed. For a quick and dirty example:
function App() {
const [texts, setTexts] = React.useState([]);
const [text, setText] = React.useState('');
React.useEffect(() => {
setTexts(['a', 'b', 'c', 'd', 'e']);
}, []);
const addDiv = () => {
setTexts([...texts, text]);
setText('');
};
return (
<div className="App">
{
texts.map((text, i) => (
<div>
<span>{text}</span>
<button onClick={() => setTexts(texts.filter((_, j) => j !== i))}>delete</button>
</div>
))
}
<button onClick={addDiv} type="button">
Click Me!
</button>
<input value={text} onChange={e => setText(e.target.value)} />
</div>
);
}
ReactDOM.render(<App />, document.querySelector('.react'));
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div class='react'></div>
Just add id in the array and using map to render
{div.map(id => (
<Div key={id} id={id} />
))}
const addDiv = () => {
setDiv([...div, uuidv4()]);
};
I have a component that I would like to test using Jest and React Testing Library. When I say test, I'm basically saying that I want to check if the content shows up on the screen. However, I'm running into a serious problem because I'm dealing with an async operation that updates the state, so the content is not appearing immediately. How would I approach this problem? A code snippet would be much appreciated.
import React, { useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import axios from 'axios';
const Home = () => {
const [tv, setTv] = useState([]);
const [tvLoading, setTvLoading] = useState(true);
// Go and fetch popular TV shows
const getPopularTv = async () => {
axios.get( ... )
setTv(data);
setTvLoading(false);
};
// This will run once. As soon as the component gets rendered for the 1st time
useEffect(() => {
getPopularTv();
}, []);
let TvData, loading;
const img_path = 'https://image.tmdb.org/t/p/w500/';
// If we have TV shows, set the 'TvData' variable to a pre-defined block of JSX using it.
if (tv && tv.total_results > 0) {
TvData = (
<div className="row animated fadeIn ">
{tv.results.slice(0, 10).map((show) => {
return (
// I WANT TO TEST IF THIS DIV APPEARS ON THE SCREEN
// SO, ON THIS DIV I'M SETTING UP THE 'data-testid'
// HOWEVER THIS IS A ASYNC OPERATION AND THE CONTENT
// WON'T SHOW UP IMMEDIATELY. HOW WOULD I TEST THIS???
<div
data-testid="home-shows" // HERE'S THE ID THAT I WANT TO USE IN MY TEST
className="col s6 m6 l6"
key={show.id}
>
<Link to={'/tvs/' + show.id}>
<img
className="responsive-img z-depth-3 poster tooltipped"
data-tooltip={show.name}
data-position="top"
src={img_path + show.poster_path}
alt={show.name}
/>
</Link>
</div>
);
})}
</div>
);
}
// Set up the 'loading' screen
loading = (
<div className="progress">
<div className="indeterminate"></div>
</div>
);
return (
<div className="container">
{tvLoading ? loading : TvData}
</div>
);
};
export default Home;
I've tried a combination of act, findByTestId, waitFor, etc. But I can't get it to work properly.
For example, I tried something like this:
it('should display TV shows', async () => {
const { getByText, findByTestId } =
render(
<BrowserRouter>
<Home />
</BrowserRouter>
)
await findByTestId('home-shows')
expect(getByText('More Info')).toBeInTheDocument();
});
My thinking was, if the content appears then it should contain the text of "More Info". If that's not the case the content is not visible, so the test should fail. however, the test fails regards if the content appears or not and I'm getting an error that I should wrap my test inside of an act() callback.
Thanks to #EstusFlask I came to a breakthrough. The solution was to use waitFor.
This is how I solved the problem:
it('should display movies', async () => {
render(
<BrowserRouter>
<Home />
</BrowserRouter>
);
const data = await waitFor(() => screen.findByTestId('home-shows'));
expect(data).toBeTruthy();
});