JV Lobo
JV Lobo

Reputation: 6316

Update parts of the UI when authenticated with Supabase and NextJS 14

I'm using NextJS 14 (App Router) and Supabase Auth (@supabase/ssr)

The authentication is working all perfectly fine, but I have parts of my UI that I would like to update when the user is authenticated. For example, I have a header.tsx component with a top bar that should show "Log In" button when the user is not authenticated and a "Log out" one when the user is. However, the state is not changing automatically when the user authenticates. If I refresh the window it works well.

I've seen there is a listener supabase.auth.onAuthStateChange that you can use to listen to auth state changes but it doesn't seem to work for me.

This is my header.tsx component:

'use client'

import { useEffect, useState } from 'react';
import { useRouter } from 'next/navigation';
import { createClient } from '@/utils/supabase/client'

export default function Header() {
  const router = useRouter()
  const supabase = createClient()
  const [userIsLogged, setuserIsLogged] = useState<boolean>(false)

  useEffect(() => {
    supabase.auth.onAuthStateChange((event, session) => {
      setTimeout(async () => {
        console.log('onAuthStateChange', event, session)
        if (event === 'SIGNED_IN') {
          setuserIsLogged(true)
        } else if (event === 'SIGNED_OUT') {
          setuserIsLogged(false)
        }
      }, 0)
    })

    const loadSession = async () => {
      const { data: { session }, error } = await supabase.auth.getSession()
      if (error) {
        console.error(error)
      }

      if (session) {
        setuserIsLogged(true)
      }
    }

    loadSession()
  }, [])


  return (
    <div>
      {userIsLogged ? (
        <form action="/auth/signout" method="post">
          <button type="submit">Sign Out</button>
        </form>
      ) : (
        <button onClick={() => router.push('/auth')}>Log in</button>
      )}
    </div>
  );
}

I have also tried returning the unsubscribe of the auth listener as I've seen recommended somewhere, something like:

const { data } = supabase.auth.onAuthStateChange((event, session) => {
  console.log(session?.user);
});

return () => {
  data.subscription.unsubscribe()
}

But it doesn't work for me either.

If any other code is needed I can show it, but I don't want this question to be very overwhelming with a lot of code that is not strictly related to the question. I'm happy to edit the question showing more code if needed :)

Upvotes: 6

Views: 1460

Answers (2)

Steven Hutchison
Steven Hutchison

Reputation: 21

I was running into this same problem.

I was using React's context api to store app profile info. I wanted it to fetch the profile data when a user logged in and share that info throughout the app. The onAuthStateChanged listener in a useEffect hook would trigger whenever I changed tabs or refreshed the page, but not when a user signed in.

My work-around was to turn my signup form into a client component and handle the submissions and login functions through a browser client instead of an ssr client. When I changed this, the onAuthStateChanged listener caught the sign in event and updated appropriately. It added a few more errors to refactor, such as redirecting with useRouter instead of redirect(), but it seems to work better than any other solution I've found so far.

Upvotes: 2

thorwebdev
thorwebdev

Reputation: 1152

Using the @supabase/ssr package will mean that authentication moves to the server side using a cookie. This means that onAuthStateChange will not work, as it's a client-side mechanism.

I'd recommend that you take a look at the Next.js Supabase Starter Template (https://github.com/vercel/next.js/blob/canary/examples/with-supabase/app/login/page.tsx#L28) there you redirect the user after successful login:

const signIn = async (formData: FormData) => {
    "use server";

    const email = formData.get("email") as string;
    const password = formData.get("password") as string;
    const supabase = createClient();

    const { error } = await supabase.auth.signInWithPassword({
      email,
      password,
    });

    if (error) {
      return redirect("/login?message=Could not authenticate user");
    }

    return redirect("/protected");
  };

Upvotes: 2

Related Questions