Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

User Authentication State doesn't match on Server and Client when Refresh Page #6

Open
myhendry opened this issue Apr 21, 2022 · 2 comments

Comments

@myhendry
Copy link

The user state doesn't match on the server and the client

When I logged in and refresh a page, I see the following error in my browser console

next-dev.js?3515:32 Warning: Text content did not match. Server: "Auth" Client: "Exit"

In my browser, my user state returns a boolean true that shows I'm authenticated; however, in my server, my user state returns a boolean false that shows I'm not authenticated

If I navigate via the client side without refreshing the page, the auth status works fine

Anyone knows why is my server and client authentication status different? I followed the example in the youtube video. Anything I missed out? Thanks

Full Repo

AuthContext.tsx

interface AuthUser extends User {
  is_subscribed: boolean;
  interval: string;
}
export interface IAuthContext {
  // setUser: Dispatch<SetStateAction<any>>;
  user: AuthUser | null;
  loginWithMagicLink: (email: string) => Promise<{ error: any | null }>;
  signOut: () => Promise<{ error: ApiError | null } | undefined>;
  isLoading: boolean;
}

export const AuthContext = createContext<IAuthContext>(null!);

interface IProps {
  supabaseClient: SupabaseClient;
}

const AuthProvider: FC<IProps> = ({ children }) => {
  const [user, setUser] = useState<AuthUser | null>(
    supabase.auth.user() as AuthUser
  );
  const [isLoading, setIsLoading] = useState<boolean>(true);
  const { push } = useRouter();

  useEffect(() => {
    const getUserProfile = async () => {
      const sessionUser = supabase.auth.user();

      if (sessionUser) {
        const { data: profile } = await supabase
          .from("profile")
          .select("*")
          .eq("id", sessionUser.id)
          .single();

        setUser({
          ...sessionUser,
          ...profile,
        });

        setIsLoading(false);
      }
    };

    getUserProfile();

    supabase.auth.onAuthStateChange(() => {
      getUserProfile();
    });

    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  useEffect(() => {
    try {
      axios.post(`/api/set-supabase-cookie`, {
        event: user ? "SIGNED_IN" : "SIGNED_OUT",
        session: supabase.auth.session(),
      });
    } catch (error) {
      console.log(error);
    }
  }, [user]);

  const loginWithMagicLink = async (email: string) => {
    const data = await supabase.auth.signIn({ email });
    return data;
  };

  const signOut = async () => {
    try {
      const data = await supabase.auth.signOut();
      setUser(null);
      push("/auth");
      return data;
    } catch (error) {
      console.log(error);
    }
  };

  return (
    <AuthContext.Provider
      value={{
        loginWithMagicLink,
        user,
        signOut,
        isLoading,
      }}
    >
      {children}
    </AuthContext.Provider>
  );
};

export const useAuth = () => {
  const context = useContext(AuthContext);
  if (context === undefined) {
    throw new Error(`useUser must be used within a UserContextProvider.`);
  }
  return context;
};

export default AuthProvider;


Navbar.tsx

export const Navbar = ({ title = "L A B" }: Props) => {
  const { user, signOut } = useAuth();
  console.log("nav isAuthenticated", !!user);

  return (
    <div className="navbar bg-base-100 shadow-lg">
      <div className="flex-1">
        <Link href="/">
          <a className="btn btn-ghost normal-case text-xl">
            <span className="text-lg font-bold tracking-widest">{title}</span>
          </a>
        </Link>
      </div>

      <div className="flex-none">
        <ul className="menu menu-horizontal p-0">
          {links.map((l) => (
            <li key={l.title} className="hidden md:block">
              <Link href={l.url}>
                <a className="cursor-pointer">{l.title}</a>
              </Link>
            </li>
          ))}

          {!!user ? (
            <li className="hidden md:block">
              <a onClick={signOut}>Exit</a>
            </li>
          ) : (
            <li className="hidden md:block">
              <Link href="/auth">
                <a className="cursor-pointer">Auth</a>
              </Link>
            </li>
          )}
          <li tabIndex={0} className="md:hidden">
            <a>
              <svg
                xmlns="http://www.w3.org/2000/svg"
                fill="none"
                viewBox="0 0 24 24"
                className="inline-block w-5 h-5 stroke-current"
              >
                <path
                  strokeLinecap="round"
                  strokeLinejoin="round"
                  strokeWidth="2"
                  d="M4 6h16M4 12h16M4 18h16"
                ></path>
              </svg>
            </a>

            <ul className="bg-base-100">
              {links.map((l) => (
                <li key={l.title}>
                  <Link href={l.url}>
                    <a
                      className="cursor-pointer tooltip tooltip-left"
                      data-tip={l.tip}
                    >
                      {l.icon}
                    </a>
                  </Link>
                </li>
              ))}
              {!!user ? (
                <li>
                  <a
                    onClick={signOut}
                    className="cursor-pointer tooltip tooltip-left"
                    data-tip="Exit"
                  >
                    <AiFillAliwangwang size={40} color="red" />
                  </a>
                </li>
              ) : (
                <li>
                  <Link href="/auth">
                    <a
                      className="cursor-pointer tooltip tooltip-left"
                      data-tip="Auth"
                    >
                      <AiFillAccountBook size={40} color="green" />
                    </a>
                  </Link>
                </li>
              )}
            </ul>
          </li>
        </ul>
        <ThemeChanger />
      </div>
    </div>
  );
};
@vbuser2004
Copy link

I was experiencing the same issue during refresh and when completing or cancelling a payment.

I believe it is caused by the user being set to supabase.auth.user() in the user.js context file when setting the initial useState. I changed this to null and the error has been eliminated. I haven't seen any other negative issue or error since making this change.

`const Provider = ({ children }) => {

const [user, setUser] = useState(null); //<---- this changed to null

const [isLoading, setIsLoading] = useState(true);

const router = useRouter();
....
`

@WeaverOfTheWeb
Copy link

You could also just set the user state back to supabase.auth.user() on logout so it keeps it consistent and provide a null user attribute if the user is logged out.

const signOut = async () => {
    try {
      const data = await supabase.auth.signOut();
      setUser(supabase.auth.user()); // will result in { user: null }
      push("/auth");
      return data;
    } catch (error) {
      console.log(error);
    }
  };

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants