Search code examples
reactjsnext.jsnext-auth

Hook requires data from another hook, but getting Error: Rendered more hooks than during the previous render


I'm getting Error:

Rendered more hooks than during the previous render.

I found some answers saying I should put all hook calls on the top.

However, in the following code, session prints undefined 3 times while it's loading until it prints the session object the fourth time.

Hence, I added a check for the loading state.

import { signIn, signOut, useSession } from "next-auth/client";
import { request } from "graphql-request";
import useSWR from "swr";

export default function Profile() {
  const [session, loading] = useSession();

  if (loading) {
    return <p className="">loading...</p>;
  }
  if (!session) {
    signIn();
  }

  const { data: user } = useSWR([USER_QUERY, session.email], (query, email) =>
    request("/api/graphql", query, { email })
  );

  return (
    <div className="">
      <p className="">{session.user.nickname}</p>
      <button onClick={() => signOut()}>Sign out</button>
    </div>
  );
}

However, this is throwing an error.

A workaround for this particular issue is to define a NextAuth callback for session, but I don't think that's the correct way to fix it, since I definitely will need to make other hook calls based on the value in the session object in the future.

How can I fix this?


Solution

  • Because hooks are tracked by array index internally by React, you can't invoke them conditionally. You need the same hook calls in the same order each time a given component renders.

    You could move the useSWR and subsequent markup to a separate component to avoid the conditional hook calls.

    In the example below I've moved the signIn call to a separate component too, but that's not strictly necessary. If you wanted to leave that inline you could just return null instead.

    export default function Profile() {
      const [session, loading] = useSession();
    
      if (loading) {
        return <p className="">loading...</p>;
      }
    
      return !session ? <SignIn /> : <UserStuff />
    }
    
    function SignIn () {
      signIn();
      return null;
    }
    
    function UserStuff () {
      const { data: user } = useSWR([USER_QUERY, session.email], (query, email) =>
        request("/api/graphql", query, { email })
      );
    
      return (
        <div className="">
          <p className="">{session.user.nickname}</p>
          <button onClick={() => signOut()}>Sign out</button>
        </div>
      );
    
    }