Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Next.js useRouter causing "Rendered more hooks than during the previous render" error in development mode while it is running smoothly in production

Have been building this NEXT.js app-router based site with next-auth. It has sign in client side page with next-auth sign in function to sign in users with credentials. I used useRouter from next/navigation to redirect the user after successful sign in. here is the code for the page.tsx -

'use client'
// all the imports
export default function LoginPage({}: Props) {
  const { data: session } = useSession()
  if (session) {
    redirect('/')
  }
  useEffect(() => {
    async function setP() {
      const res = await getProviders()
      setProviders(res)
    }
    setP()
  }, [])

  const [providers, setProviders] = useState<Record<
    LiteralUnion<BuiltInProviderType, string>,
    ClientSafeProvider
  > | null>(null)

  const email = useRef('')
  const password = useRef('')
  const {
    headingsContainer,
    mainContainer,
    register,
    loginBtnContainer,
    thirdPartyLoginContainer,
    forgetPass,
  } = styles
  const router = useRouter()

  async function handleSubmit(e: FormEvent<HTMLFormElement>) {
    e.preventDefault()
    const loginId = toast.loading('Checking your credentials...')
    const result = await signIn('credentials', {
      email: email.current,
      password: password.current,
      redirect: false,
    })
    if (result?.error) {
      toast.update(loginId, {
        render: 'Wrong Email or Password!...',
        type: 'error',
        isLoading: false,
        autoClose: 3000,
      })
    } else {
      toast.update(loginId, {
        render: '🦄 Logged In!',
        type: 'success',
        isLoading: false,
        autoClose: 3000,
      })
      router.replace('/') // <--- this causes the error
    }
  }
  return (
    <div className={mainContainer}>
      <form onSubmit={(e) => handleSubmit(e)}>
        <div className={headingsContainer}>
          <h3>Welcome Back</h3>
        </div>
        {/* <label htmlFor="email">Your Email</label> */}
        <input
          type="text"
          placeholder="Enter Email"
          name="email"
          onChange={(e) => {
            email.current = e.target.value
          }}
          required
        />

        <br />
        <br />

        {/* <label htmlFor="pswrd">Your password</label> */}
        <input
          type="password"
          placeholder="Enter Password"
          name="pswrd"
          onChange={(e) => {
            password.current = e.target.value
          }}
          required
        />
        <br />
        <br />
        <br />
        <div className={loginBtnContainer}>
          <button type="submit">Login</button>
        </div>
        <div>
          <p className={forgetPass}>
            <a href="#">Forgot Password?</a>
          </p>
        </div>
        <p className={register}>
          Not registered? <Link href="/auth/signup">Register here!</Link>
        </p>
      </form>

      <div className={thirdPartyLoginContainer}>
        {providers
          ? Object.values(providers).map((provider) =>
              provider.name !== 'Credentials' ? (
                <div key={provider.name}>
                  <button onClick={() => signIn(provider.id)}>
                    <Image
                      src={`https://authjs.dev/img/providers/${provider.id}-dark.svg`}
                      width={24}
                      height={24}
                      alt={`${provider.name} logo`}
                    />
                    Continue with {provider.name}
                  </button>
                </div>
              ) : null
            )
          : null}
      </div>
    </div>
  )
}

This should work and redirect the user after a successful sign in but it throws the following errors -

enter image description here enter image description here

help is extremely appreciated and if needed any furthur info about the code pls let me know in the comments.

like image 969
Kartik Thakur Avatar asked Dec 30 '25 15:12

Kartik Thakur


2 Answers

In my case, I had two strange situations that had nothing to do with hooks at all, despite the misleading error message.


1. Redirecting from React Server Components

I had this error in an App Router based Next.js project. I had checked all over for any "conditionally rendered hooks" but there are NONE.

Apparently, this can happen if you're using redirect method from next/navigation in a React Server Component. I don't know why this is the case, but it is.

import { redirect } from 'next/navigation'

...

// inside component:

if (condition) redirect('/some/path')

return <div> ... </div>

The solution is to create a separate client component called ClientRedirect, and use that to redirect using client. Then in your RSC, you can use it as such:

import ClientRedirect from '@/components/ClientRedirect' // or wherever

...

// inside component:

if (condition) return <ClientRedirect href={'/some/path'} />

return <div> ... </div>

This is a sample code for the client component:

'use client'
import { useRouter } from 'next/navigation'
import type { Route } from 'next'

/**
 * For client-side redirects in React Server Components (RSC)
 *
 * Trying to redirect in server using `redirect` from `next/navigation`
 * gives an odd error "Rendered more hooks than during the previous render"
 * which seems impossible to debug, as we don't have any hooks that are
 * conditionally rendered or skipped. There's no solution available for this
 * specific problem; most solutions point to checking for conditional hooks.
 *
 * At present, client-based redirection works correctly, therefore we are
 * creating a client component whose function is purely to redirect. It
 * doesn't return any JSX.Element. Note that we should return this component
 * early, otherwise the other components will flash while redirecting.
 *
 * @param {Route} href - The route path to which the client will be redirected.
 * @returns {null} - Returns null as void is not assignable to JSX.Element.
 */
export default function ClientRedirect({ href }: { href: Route }): null {
  const router = useRouter()
  router.push(href)
  return null
}

Note: Typed Routes is an experimental feature. If you're not using it, change Route to string and remove the import line.


2. Chaining Redirects from Client through Server

In this situation, a button (in an error page) was calling router.push('/') and this page route sets window.location.href = '/resource' (example), and then the resource path is a server API route that calls redirect('/resource/${resourceId}') where the resource ID depends on the logged in user.

Apparently this wouldn't work and it produces the same error as well about having more hooks than previous render (despite having no bad hook setup).

The solution here was to change the button's click handler to set window.location.href = '/' (or directly '/resource') instead.

like image 130
ADTC Avatar answered Jan 02 '26 03:01

ADTC


The "rendered more hooks" error occurs due to the fact that you abort rendering before all hooks get called (your redirect call comes before the useEffect hook). Another issue here is that you probably want to call redirect only during the initial render, not right after your user signed in and your page re-renders with valid session object.

Here is how you can implement proper rendering flow.


const router = useRouter();
const { data: session, status: sessionStatus } = useSession();
const [signingIn, setSigningIn] = useState(false);

const shouldRedirect = !signingIn && session;

useEffect(() => {
  if (shouldRedirect) {
    router.push("/");
  }
}, [router, shouldRedirect]);

if (sessionStatus === "loading" || shouldRedirect) return null;

async function handleSubmit(e) {
  e.preventDefault();

  setSigningIn(true);

  const result = await signIn("credentials", {
    redirect: false,
    ...
  });

  if (result?.error) {
    // handle error

    setSigningIn(false);
  } else {
    router.push("/");
  }
}
like image 35
Igor Danchenko Avatar answered Jan 02 '26 04:01

Igor Danchenko



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!