DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’» is a community of 964,423 amazing developers

We're a place where coders share, stay up-to-date and grow their careers.

Create account Log in
Cover image for Error: Hydration failed because the initial UI does not match what was rendered on the server.
Rushab jain
Rushab jain

Posted on

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

Take me to the fix

Where I encountered the bug-

export default function Nav() {
const user = getUser();

{user ? <AuthenticatedNav /> : <UnauthenticatedNav />} //this throws error
}
Enter fullscreen mode Exit fullscreen mode

error shown in the browser window:Error: Hydration failed because the initial UI does not match what was rendered on the server
This error notice provides no information that could lead us to problematic code, but the console does.
same as above but more verbose

but why?

jakie chan confusion gif

What is causing this error?

While rendering your application, there was a difference between the React tree that was pre-rendered (SSR/SSG) and the React tree that was rendered during the first render in the Browser. The first render is called Hydration which is a feature of React.
This can cause the React tree to be out of sync with the DOM and result in unexpected content/attributes being present. -nextjs docs

yes I kinda get it but still why

All I want to do is this-

If the user is logged in, render the <AuthenticatedNav> component; otherwise, render the <UnauthenticatedNav> component.

let us understand what is "hydration" and why is it used-

Server-side rendering (SSR) is used by frameworks such as nextjs to increase performance (LCP & FCP) and user experience (SEO) it renders the app on the server first, duh! It returns a fully-formed HTML document to the user but apps are "dynamic" and not everything can be accomplished by HTML & CSS so We tell React to attach event handlers to the HTML to make the app interactive.
This process of rendering our components and attaching event handlers is known as β€œhydration”. It is like watering the "dry" HTML with the "water" of interactivity(JS). After hydration, our application becomes interactive or "dynamic".
hydration and rehydration are often used interchangeably but during rehydration, the client-side JS includes the same React code used to generate it at compile time.
It runs on the user's device and builds up a picture of what the world should look like. It then compares it to the HTML built into the document. This is a process known as rehydration

In a rehydration, React assumes that the DOM won't change. It's just trying to adapt the existing DOM
When a React app rehydrates, it assumes that the DOM structure will match. If it doesn't then you know what
console error Error: Hydration failed because the initial UI does not match what was rendered on the server.
so in our case, we cannot possibly know the user state on the server and user state will return undefined until it is mounted on the client

So how to fix this -

essentially we have two ways to Fix this

  1. useEffect/useMounted hooks
  2. wrapping in a component

useEffect or useMounted custom hook

export default function Nav() {
  const [hasMounted, setHasMounted] = React.useState(false);
  React.useEffect(() => {
    setHasMounted(true);
  }, []);
  if (!hasMounted) {
    return null;
  }
  const user = getUser();
{user ? <AuthenticatedNav /> : <UnauthenticatedNav />}
Enter fullscreen mode Exit fullscreen mode

OR

useHasMounted custom hook

function useHasMounted() {
  const [hasMounted, setHasMounted] = React.useState(false);
  React.useEffect(() => {
    setHasMounted(true);
  }, []);
  return hasMounted;
}
Enter fullscreen mode Exit fullscreen mode
Wrapping in a component

<clientOnly> component

function ClientOnly({ children, ...delegated }) {
  const [hasMounted, setHasMounted] = React.useState(false);
  React.useEffect(() => {
    setHasMounted(true);
  }, []);
  if (!hasMounted) {
    return null;
  }
  return (
    <div {...delegated}>
      {children}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Then you can wrap it around whatever compenent you want to render client-side only-

<ClientOnly>
  <Nav/>
</ClientOnly>
Enter fullscreen mode Exit fullscreen mode
What I did -

nav.js

import { UserState } from '../../context/userProvider'

export default function Nav() {
 UserState() ? <AuthenticatedNav /> : <UnauthenticatedNav />
}
Enter fullscreen mode Exit fullscreen mode

userProvider.js //using a global user context provider

export function UserState() {
 const { user } = useUser();
 const [isUserLoggedIn, setIsUserLoggedIn] = useState(false);
  useEffect(() => {
     setIsUserLoggedIn(!!user);
   }, [user]);
  return isUserLoggedIn;
}
Enter fullscreen mode Exit fullscreen mode

πŸ™Thank you for reading I hope you found this informative; if you have any criticism, suggestions, or corrections, please leave them in the comments section.

untill next time

Latest comments (0)

🌚 Browsing with dark mode makes you a better developer.

It's a scientific fact.