Take me to the fix
Where I encountered the bug-
export default function Nav() {
const user = getUser();
{user ? <AuthenticatedNav /> : <UnauthenticatedNav />} //this throws error
}
This error notice provides no information that could lead us to problematic code, but the console does.
but why?
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
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
- useEffect/useMounted hooks
- 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 />}
OR
useHasMounted
custom hook
function useHasMounted() {
const [hasMounted, setHasMounted] = React.useState(false);
React.useEffect(() => {
setHasMounted(true);
}, []);
return hasMounted;
}
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>
);
}
Then you can wrap it around whatever compenent you want to render client-side only-
<ClientOnly>
<Nav/>
</ClientOnly>
What I did -
nav.js
import { UserState } from '../../context/userProvider'
export default function Nav() {
UserState() ? <AuthenticatedNav /> : <UnauthenticatedNav />
}
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;
}
π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.
Oldest comments (0)