Next.js Discord

Discord Forum

Hydration Error in Form

Answered
Amur catfish posted this in #help-forum
Open in Discord
Amur catfishOP
Hello, in the login form of my site i encounter this error.
Unhandled Runtime Error
Error: Hydration failed because the initial UI does not match what was rendered on the server.
See more info here: https://nextjs.org/docs/messages/react-hydration-error

Did not expect server HTML to contain a <div> in <div>.


<Login>
  <div>
  ^^^^^
    <div>
    ^^^^^

problem being my component/form is pretty basic:
const Login = () => {
  return (
    <div className="">
      <div className="w-full bg-formideo-white">

        <h1 className="text-xl font-bold font-poppins mb-6">
          Connectez-vous
        </h1>
        <form className="">
          <input type="email" placeholder="E-mail" className={inputStyle} />
          <input type="password" placeholder="Mot de passe" className={inputStyle}  />
          <div className="flex flex-col mt-4 items-end">
            <Link className="text-primary-green text-sm font-medium font-poppins" href="/forgot-password">Mot de passe oublié ?</Link>
            <ButtonComponent
              buttonLabel="Connexion"
              buttonStyle="w-full text-white text-sm font-bold font-poppins justify-center"
              buttonContainerStyle=" h-[50px] px-[18px] py-3.5 bg-formideo-custom-gradient rounded-lg mt-6 w-full"
              type="submit"
            />
          </div>
        </form>
      </div>
    </div>
  );
};

export default Login;

I've isolated the problem to the 2 inputs, when both are commented, no hydration error happens.
When one or the other are commented there's no hydration error.
when both are active, there's hydration error.
when placeholders are removed hydration error is still there.
when placeholders are removed and ONE or both of the inputs are changed to text, the error is no longer there.
when inputs are both changed to text, but there's a placeholder, i get a hydration error.
What can be causing this? i\m trying to bugfix but i can't find any logic
Answered by Cape lion
You should do this
const LoginForm = dynamic(() => import('../ui/login/LoginForm'), {
    ssr: false
  });
outside of your component
View full answer

90 Replies

Cape lion
@Amur catfish I think the issue lies with your div container with an empty class. I see no reason for using that since you already have one div wrapping both the h1 and form tags. Try to remove the outer div. It should work.
You will mostly face such errors when you have repetitive and same html tags as parent and child.
@Cape lion <@1215252828734488610> I think the issue lies with your div container with an empty class. I see no reason for using that since you already have one div wrapping both the `h1` and `form` tags. Try to remove the outer div. It should work.
Amur catfishOP
hello, thank you for answering, indeed after i removed the starting div my component worked.
but here comes the issue or confusion.
why can't i encapsulate the page in a div? feels counter intuitive that my Page for Login starts as <Form>
why should the title H1 be inside the Form?
normally or what i understood is that my pages are to be encapsulated in one <div> .
If i were to create a <FormComponent> it would also be rendered under the <div> by the parent eventually, i fail to understand the logic in why does this div cause that
Amur catfishOP
const Login = () => {
  return (
    <form className="">
      <h1 className="text-xl font-bold font-poppins mb-6">
        Connectez-vous
      </h1>
      <input type="email" className={inputStyle} />
      <input type="password" className={inputStyle} />
      <div className="flex flex-col mt-4 items-end">
        <Link className="text-primary-green text-sm font-medium font-poppins" href="/forgot-password">Mot de passe oublié ?</Link>
        <ButtonComponent
          buttonLabel="Connexion"
          buttonStyle="w-full text-white text-sm font-bold font-poppins justify-center"
          buttonContainerStyle=" h-[50px] px-[18px] py-3.5 bg-formideo-custom-gradient rounded-lg mt-6 w-full"
          type="submit"
        />
      </div>
    </form>
  );
};

export default Login;

like this it works:
but like this:
const Login = () => {
  return (
      <h1 className="text-xl font-bold font-poppins mb-6">
        Connectez-vous
      </h1>
    <form className="">
      <input type="email" className={inputStyle} />
      <input type="password" className={inputStyle} />
      <div className="flex flex-col mt-4 items-end">
        <Link className="text-primary-green text-sm font-medium font-poppins" href="/forgot-password">Mot de passe oublié ?</Link>
        <ButtonComponent
          buttonLabel="Connexion"
          buttonStyle="w-full text-white text-sm font-bold font-poppins justify-center"
          buttonContainerStyle=" h-[50px] px-[18px] py-3.5 bg-formideo-custom-gradient rounded-lg mt-6 w-full"
          type="submit"
        />
      </div>
    </form>
  );
};

export default Login;
it doesn't
does that mean all my forms must use the complete of the page or am i missing something?
Cape lion
No no, in your original code, you had two div tags. You just have to remove the outer tag so the result would be:
const Login = () => {
  return (
    <div className="w-full bg-formideo-white">
        <h1 className="text-xl font-bold font-poppins mb-6">
          Connectez-vous
        </h1>
        <form className="">
          <input type="email" placeholder="E-mail" className={inputStyle} />
          <input type="password" placeholder="Mot de passe" className={inputStyle}  />
          <div className="flex flex-col mt-4 items-end">
            <Link className="text-primary-green text-sm font-medium font-poppins" href="/forgot-password">Mot de passe oublié ?</Link>
            <ButtonComponent
              buttonLabel="Connexion"
              buttonStyle="w-full text-white text-sm font-bold font-poppins justify-center"
              buttonContainerStyle=" h-[50px] px-[18px] py-3.5 bg-formideo-custom-gradient rounded-lg mt-6 w-full"
              type="submit"
            />
          </div>
        </form>
      </div>
  );
};

export default Login;
Now if that gives you an error as well, means you are wrapping your Login component inside another div tag.
Yup it won't make sense to nest h1 and other tags unrelated to form inside form tags so you would usually wrap your form component inside a div tag and nest other tags inside it. I also do that as well.
From your original code, you only remove your outermost div and keep the inner div that wraps both your h1 and form tags
@Cape lion Now if that gives you an error as well, means you are wrapping your `Login` component inside another `div` tag.
Amur catfishOP
my Login is just a Login page, so it goes like Layout --> login ---> form.
i tried these 2 approachs:
const Login = () => {
  return (
    <form className="">
      <h1 className="text-xl font-bold font-poppins mb-6">
        Connectez-vous
      </h1>
      <input type="email" className={inputStyle} />
      <input type="password" className={inputStyle} />
      <div className="flex flex-col mt-4 items-end">
        <Link className="text-primary-green text-sm font-medium font-poppins" href="/forgot-password">Mot de passe oublié ?</Link>
        <ButtonComponent
          buttonLabel="Connexion"
          buttonStyle="w-full text-white text-sm font-bold font-poppins justify-center"
          buttonContainerStyle=" h-[50px] px-[18px] py-3.5 bg-formideo-custom-gradient rounded-lg mt-6 w-full"
          type="submit"
        />
      </div>
    </form>
  );
};

    <div>

      <form className="">
        <h1 className="text-xl font-bold font-poppins mb-6">
          Connectez-vous
        </h1>
        <input type="email" className={inputStyle} />
        <input type="password" className={inputStyle} />
        <div className="flex flex-col mt-4 items-end">
          <Link className="text-primary-green text-sm font-medium font-poppins" href="/forgot-password">Mot de passe oublié ?</Link>
          <ButtonComponent
            buttonLabel="Connexion"
            buttonStyle="w-full text-white text-sm font-bold font-poppins justify-center"
            buttonContainerStyle=" h-[50px] px-[18px] py-3.5 bg-formideo-custom-gradient rounded-lg mt-6 w-full"
            type="submit"
          />
        </div>
      </form>
    </div>
the second approach throws the hydration error.
Unhandled Runtime Error
Error: Hydration failed because the initial UI does not match what was rendered on the server.
See more info here: https://nextjs.org/docs/messages/react-hydration-error

Did not expect server HTML to contain a <div> in <div>.


<Login>
  <div>
  ^^^^^
Cape lion
If that's the case can you show me the snippet from layout?
Can only mean that you must be doing <div>{children}</div> in layout. Otherwise there should not be an error.
@Cape lion If that's the case can you show me the snippet from layout?
Amur catfishOP
interface RootLayoutProps {
  children: ReactNode;
}
export default async function RootLayout({ children }: RootLayoutProps) {
  const footerData = await fetchAllProfessions();
  const navigationData = navBarData as NavBarProps;
  return (
    <html lang="fr">
      <body className={`bg-primary ${poppins.variable} ${sofia.variable}`}>
        <HeaderTop
          profession={navigationData.profession}
          display='flex lg:hidden  w-auto sticky top-0 z-50'
        />
        {children}
        <Suspense fallback={<div>Loading....</div>}>
          <FooterComponent
            navigationLinks={footerData}
          />
        </Suspense>
      </body>
    </html>
  );
}
i didn't create a layout for Login as it did not use a navbar at all so i went straight for the page ...but i think there might be the issue
my other layouts have a <main> declaration and i apparently missed it on my login...
    return (
        <div className='m-3 lg:m-8'>
            <Header
                interiorWrapperStyle='h-[120dvh] md:h-[120dvh] lg:h-[80dvh] lg:min-h-[800px] lg:rounded-xl lg:bg-gradient-card'
                exteriorWrapperStyle='h-[120dvh] md:h-[120dvh] lg:h-[80dvh] relative lg:min-h-[800px]'
                imageDesktopClassname="w-auto absolute bottom-0 right-0 hidden md:h-[20vh] lg:block lg:h-[60vh] xl:h-[70vh] min-h-[350px]"
                imageMobileClassname="w-auto h-[55dvh] sm:h-[65vh] md:h-[60vh] absolute bottom-0 flex lg:hidden"
                imageDesktopHeight={852}
                imageDesktopWidth={883}
                imageMobileHeight={330}
                imageMobileWidth={320}
                srcDesktop={CatalogueFooterData.desktopPicture}
                srcMobile={CatalogueFooterData.mobilePicture}
                footerContainerStyle='flex justify-center'
            >
                <CatalogueHeaderBody
                    data={catalogueBodyData}
                />
            </Header>
            <main>
                {children}
            </main>
        </div>
    );
Cape lion
Is your repo public?
If your login page doesn't have a layout, it will take the layout of the nearest layout.tsx file so might have to take a look at there.
Also try to restart the server. Helps sometimes.
@Cape lion Is your repo public?
Amur catfishOP
nope, private for my jbo
but htank you, you insight was indeed the issue
as i had my login page not inside <main>
ah ok wasn't that, i tried writing <main> inside the page and same problem occured
Cape lion
Is the issue fixed by the way?
Amur catfishOP
nope, i tried this
const Login = () => {
  return (
<main>
      <form className="">
        <h1 className="text-xl font-bold font-poppins mb-6">
          Connectez-vous
        </h1>
        <input type="email" className={inputStyle} />
        <input type="password" className={inputStyle} />
        <div className="flex flex-col mt-4 items-end">
          <Link className="text-primary-green text-sm font-medium font-poppins" href="/forgot-password">Mot de passe oublié ?</Link>
          <ButtonComponent
            buttonLabel="Connexion"
            buttonStyle="w-full text-white text-sm font-bold font-poppins justify-center"
            buttonContainerStyle=" h-[50px] px-[18px] py-3.5 bg-formideo-custom-gradient rounded-lg mt-6 w-full"
            type="submit"
          />
        </div>
      </form>
</main>
  );
};

export default Login;
and i get the hydration error
is there some kind of logic i'm missing, or some way to inspect my element?
Cape lion
Okay send me the full error message
Then i will walk you through with inspecting element...
Amur catfishOP
Unhandled Runtime Error
Error: Hydration failed because the initial UI does not match what was rendered on the server.
See more info here: https://nextjs.org/docs/messages/react-hydration-error

Did not expect server HTML to contain a <div> in <main>.


<Login>
  <main>
Root Layout:
// src/app/layout.tsx
import React, { ReactNode } from 'react';
import "./globals.css";
import { poppins, sofia } from "@src/app/fonts";
import { Suspense } from 'react';
import FooterComponent from '@src/app/ui/footer/FooterComponent';
import { fetchAllProfessions } from './lib/api/fetchOperations';
import { navBarData } from "@src/app/utils/localDb/placeholderData";
import { NavBarProps } from '@src/app/ui/header/Navbar';
import HeaderTop from '@src/app/ui/header/HeaderTop';
interface RootLayoutProps {
  children: ReactNode;
}
export default async function RootLayout({ children }: RootLayoutProps) {
  const footerData = await fetchAllProfessions();
  const navigationData = navBarData as NavBarProps;
  return (
    <html lang="fr">
      <body className={`bg-primary ${poppins.variable} ${sofia.variable}`}>
        <HeaderTop
          profession={navigationData.profession}
          display='flex lg:hidden  w-auto sticky top-0 z-50'
        />
        {children}
        <Suspense fallback={<div>Loading....</div>}>
          <FooterComponent
            navigationLinks={footerData}
          />
        </Suspense>
      </body>
    </html>
  );
}
/Login
import Image from "next/image";
import Link from "next/link";
import ButtonComponent from "@src/app/components/common/buttons/ButtonComponent";
const inputStyle = "w-full p-2 rounded-lg border border-slate-200 mb-2 font-poppins bg-gray-50";
const Login = () => {
  return (
    <main>
      <form className="">
        <h1 className="text-xl font-bold font-poppins mb-6">
          Connectez-vous
        </h1>
        <input type="email" className={inputStyle} />
        <input type="password" className={inputStyle} />
        <div className="flex flex-col mt-4 items-end">
          <Link className="text-primary-green text-sm font-medium font-poppins" href="/forgot-password">Mot de passe oublié ?</Link>
          <ButtonComponent
            buttonLabel="Connexion"
            buttonStyle="w-full text-white text-sm font-bold font-poppins justify-center"
            buttonContainerStyle=" h-[50px] px-[18px] py-3.5 bg-formideo-custom-gradient rounded-lg mt-6 w-full"
            type="submit"
          />
        </div>
      </form>
    </main>
  );
};

export default Login;
Amur catfishOP
as soon as my form is encapsulated by anything
i get the hydration error
even after i gave it a layout like the other pages
Cape lion
What is your nextjs version?
Also for now you can remove the main tag, open devtool, and send me a screenshot of the html elements till the form tag like one above
Amur catfishOP
Next.js (14.2.4)
okay
@Amur catfish Click to see attachment
Amur catfishOP
it renders this
i also tried with what it says in the doc:
suppressHydrationWarning and it doesn't change anything
Cape lion
Could you remove that className prop from form tag or try giving it some value
If nothing works there is one final solution that will work.
Amur catfishOP
const Login = () => {
  return (
    <div>

      <form className="w-3/4" suppressHydrationWarning={true} >
        <h1 className="text-xl font-bold font-poppins mb-6">
          Connectez-vous
        </h1>
        <input type="email" className={inputStyle} />
        <input type="password" className={inputStyle} />
        <div className="flex flex-col mt-4 items-end">
          <Link className="text-primary-green text-sm font-medium font-poppins" href="/forgot-password">Mot de passe oublié ?</Link>
          <ButtonComponent
            buttonLabel="Connexion"
            buttonStyle="w-full text-white text-sm font-bold font-poppins justify-center"
            buttonContainerStyle=" h-[50px] px-[18px] py-3.5 bg-formideo-custom-gradient rounded-lg mt-6 w-full"
            type="submit"
          />
        </div>
      </form>
    </div>
  );
};

export default Login;
that's with something inside, gonna remove aswell
same issue without className
Cape lion
Okay try this, take all the code inside Login component into a new component LoginForm and use that inside your login page like so <LoginForm />
If there is till try error, mark the LoginForm component with use client. And if it still has error then the final solution is the tell nextjs not to pre-render the login form component. So let's try above before we make that change.
Amur catfishOP
'use client sends error too
'use client'
import Link from "next/link";
import ButtonComponent from "@src/app/components/common/buttons/ButtonComponent";
const inputStyle = "w-full p-2 rounded-lg border border-slate-200 mb-2 font-poppins bg-gray-50";
const LoginForm = () => {
    return (<form suppressHydrationWarning={true} >
        <h1 className="text-xl font-bold font-poppins mb-6">
            Connectez-vous
        </h1>
        <input type="email" className={inputStyle} />
        <input type="password" className={inputStyle} />
        <div className="flex flex-col mt-4 items-end">
            <Link className="text-primary-green text-sm font-medium font-poppins" href="/forgot-password">Mot de passe oublié ?</Link>
            <ButtonComponent
                buttonLabel="Connexion"
                buttonStyle="w-full text-white text-sm font-bold font-poppins justify-center"
                buttonContainerStyle=" h-[50px] px-[18px] py-3.5 bg-formideo-custom-gradient rounded-lg mt-6 w-full"
                type="submit"
            />
        </div>
    </form>);
}
export default LoginForm; 
@Cape lion Also for now you can remove the `main` tag, open devtool, and send me a screenshot of the html elements till the `form` tag like one above
Cape lion
This screenshot also uses same version of nextjs as yours and as you can see here as well there are nested div tags, so it's kinda weird that you are receing the hydration error.
Amur catfishOP
why is all this comming from a <form> ?
it is specially if there's a div encapsulating my <form> doesn't make any logical sense
@Amur catfish it is specially if there's a div encapsulating my <form> doesn't make any logical sense
Cape lion
Yes you are right it should not throw that error.
By the way your login page is not marked as use client right?
Amur catfishOP
no
the page not
the component yes
Cape lion
Alright have you used nextjs dyamic function?
Amur catfishOP
nope
Cape lion
Inside you login page import this import dynamic from 'next/dynamic'
Next: do this const ComponentA = dynamic(() => import('../components/A')), ComponentA is your component name so should be LoginForm in your case and give the relative path to your login form component
const ComponentC = dynamic(() => import('../components/C'), { ssr: false })
Add that {ssr: false} option as well
Amur catfishOP
C would be what?
Cape lion
../components/C replace all this with path to your login form component
Amur catfishOP
wasn't t that A?
i got this:
import dynamic from 'next/dynamic'

const Login = () => {
  const LoginForm = dynamic(() => import('../ui/login/LoginForm'), {
    ssr: false
  });
  return (
    <LoginForm />
  );
};

export default Login;
Cape lion
You should do this
const LoginForm = dynamic(() => import('../ui/login/LoginForm'), {
    ssr: false
  });
outside of your component
Answer
Cape lion
Now the error should disappear
Amur catfishOP
So like this?
Cape lion
Yup true.
Amur catfishOP
it works boss, thank you
but i don't really get why we encountered this in the first place
i guess for a Login form to disable SSR won't hurt the SEO much?
@Amur catfish but i don't really get why we encountered this in the first place
Cape lion
I also have no idea haha. From my opinion, it shouldn't be throwing any error. For your information, the reason why it worked because of our final solution is because by loading the component dynamically, we are saying nextjs not to pre-render our login form component on the server by setting the flag: {ssr: false}. Now nextjs don't have to compare the html on the server vs client so no hydration error. By default in nextjs, even if your component is a client component, it will pre-render on the server so we are disabling that feature for your login form.
Amur catfishOP
should i remove 'use client' from the component and just leave the ssr: false?
@Amur catfish i guess for a Login form to disable SSR won't hurt the SEO much?
Cape lion
As for the SEO, in your login form component, you only keep the contents inside form tag there and move your h1 tag up to your login page like so.
const Login = () => {
  return (
    <div>
<h1>Login</h1>
  <LoginForm />
</div>
  );
};
. Now your h1 tag always render on the server and only the form is rendered on the client. Plus you can also configure metadata for your login page as well to enhance SEO. That way you can still have better SEO while making your form entirely client component
You still need to keep use client directive in your LoginForm component.
I believe later on you will make use react hooks for form validations and all? so
Amur catfishOP
i see, thank you!, one last question should recover pass / connection button be part of the <form> or also outside it?
Cape lion
You can keep that inside the form component as those aren't really necessary for SEO. Feel free to mention me if you have any other nextjs related issues/questions. Happy to help.
Amur catfishOP
thank you very much!
Cape lion
@Amur catfish I just found this answer from reddit: Deleting .next folder and starting localhost again will simply fix it. Next time you face the same issue try this and also you can check for the current issue we discussed as well.
the error came back
i guess i'll have to stay w that