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
Not sure why I'm getting this error, the following is my Login view route component, and I've tried returning just the <AuthForm />
, but also tried 2 different ways of disabling SSR.
'use client';
import dynamic from 'next/dynamic';
// import AuthForm from '@/components/AuthForm';
// const DynamicAuthForm = dynamic(() => import('@/components/AuthForm'), {
// ssr: false,
// });
const NoSSR = dynamic(() => import('@/components/AuthForm'), { ssr: false });
const LoginPage = () => {
return (
<div>
<NoSSR />;
</div>
);
// return <DynamicAuthForm />;
// return <AuthForm />;
};
export default LoginPage;
And my AuthForm.tsx component:
'use client';
import { useState } from 'react';
import { useRouter } from 'next/navigation';
import Link from 'next/link';
import callFetch from '@/utils/fetch/callFetch';
const apiUrl = process.env.NEXT_PUBLIC_API_DEV_LOGIN || '';
if (!apiUrl) {
throw new Error('API URL is not defined');
}
function AuthForm() {
const router = useRouter();
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [isSubmitting, setIsSubmitting] = useState(false);
// Create a function to handle form submission
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
setIsSubmitting(true);
const formBody = JSON.stringify({ user: email, password });
try {
const response = await callFetch(apiUrl, 'POST', formBody);
if (!response.ok) {
setIsSubmitting(false);
// TODO - handle error in notification alert
throw new Error(
`Login failed: ${response.status} - ${response.statusText}`
);
}
console.log('login response:', response);
setIsSubmitting(false);
// Redirect to postings upon successful login
router.push('/postings');
} catch (error) {
console.error('Error logging in:', error);
setIsSubmitting(false);
// Handle error, show error message, etc.
}
};
return (
<>
<div className="login-container">
{/* <div className="form"> */}
<form onSubmit={handleSubmit}>
Login to
<Link href="/">
<h1 className="logo">
Bounty<strong>Jobs</strong>
</h1>
</Link>
<div>
<label htmlFor="email">Email</label>
<input
type="email"
id="email"
value={email}
onChange={e => setEmail(e.target.value)}
required
/>
</div>
<div>
<label htmlFor="password">Password:</label>
<input
type="password"
id="password"
value={password}
onChange={e => setPassword(e.target.value)}
required
/>
</div>
<div className="actions">
<button disabled={isSubmitting}>Submit</button>
</div>
</form>
{/* </div> */}
</div>
</>
);
}
export default AuthForm;
I don't see any issue anywhere in my markup:
return (
<>
<div className="login-container">
{/* <div className="form"> */}
<form onSubmit={handleSubmit}>
Login to
<Link href="/">
<h1 className="logo">
Bounty<strong>Jobs</strong>
</h1>
</Link>
<div>
<label htmlFor="email">Email</label>
<input
type="email"
id="email"
value={email}
onChange={e => setEmail(e.target.value)}
required
/>
</div>
<div>
<label htmlFor="password">Password:</label>
<input
type="password"
id="password"
value={password}
onChange={e => setPassword(e.target.value)}
required
/>
</div>
<div className="actions">
<button disabled={isSubmitting}>Submit</button>
</div>
</form>
{/* </div> */}
</div>
</>
);
The problem was my RootLayout of /login
Original code:
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body>
<div>{children}</div>
</body>
</html>
);
}
I removed the html
and body
tags here, which probably duplicated themselves since my app layout also has html and body.
This fixed the issue.