I have a client-side React component with an explicit use client
declared in it:
"use client"
import { useForm } from "react-hook-form"
import { zodResolver } from "@hookform/resolvers/zod"
import * as z from "zod"
import { useState, useTransition } from "react"
import { useSearchParams } from "next/navigation"
import { LoginSchema } from "@/schemas"
import login from "@/app/actions/login"
export default function LoginForm() {
const currentURL = `/${window.location.href.split("/").slice(3).join("/")}`
const searchParams = useSearchParams()
const callbackUrl = searchParams.get("callback") || currentURL
const [isPending, startTransition] = useTransition()
const form = useForm<z.infer<typeof LoginSchema>>({
resolver: zodResolver(LoginSchema),
defaultValues: {
email: "",
password: "",
},
})
const onSubmit = (values: z.infer<typeof LoginSchema>) => {
startTransition(() => {
login(values, callbackUrl)
.then((data) => {
if (data?.error) {
form.reset()
setError(data.error)
}
if (data?.success) {
form.reset()
setSuccess(data.success)
}
if (data?.twoFactor) {
setShowTwoFactor(true)
}
})
.catch(() => setError("Something went wrong"))
})
}
return (
<form className="space-y-6" onSubmit={form.handleSubmit(onSubmit)}>
...
</form>
);
}
I am getting the following error when trying to access the URL via window.location.href
:
ReferenceError: window is not defined at LoginForm (./src/components/auth/LoginForm.tsx:40:28)
My understanding is that the window object is only available on the client so this should have worked. What am I doing wrong?
What I tried
I added a console.log()
inside the function to print out the evaluated value of currentURL
like so:
const currentURL = `/${window.location.href.split("/").slice(3).join("/")}`
console.log(currentURL)
And this worked just fine. So the component is clearly able to access the window
object. But the server is still throwing the error mentioned above. In fact, I don’t understand why the server is even giving the error considering the component is being rendered on the client!
2
Answers
I finally managed to resolve the issue by moving the
callbackUrl
logic into theonSubmit
method:This way, the URL is retrieved and slug extracted at the time of form submission rather than component mount or render. Using an
if()
, I test whether there’s a searchParam value. If yes, I send that. But if not, I send the computedcurrentUrl
value.Just because the component is marked with
use client
directive doesn’t mean it doesn’t render on the server as well as the client.When client components are used, pre-rendered static HTML is still generated on the server to optimise the initial payload. That will be why you are seeing the error in the server logs.
The docs explain the rendering process here https://nextjs.org/docs/app/building-your-application/rendering/client-components#how-are-client-components-rendered
When using
window
object, it always needs to be in a place where it is only executed on the client, like a useEffect or event handler