Search code examples
javascriptreactjstypescriptnext.jssupabase

How can I handle these user sessions of the Logout button in Next.js to render it inside the Navbar components correctly?


I have these AuthButton done in server and client-side:

Client

'use client';

import { Session, createClientComponentClient } from '@supabase/auth-helpers-nextjs';
import Link from 'next/link';
import { useRouter } from 'next/navigation';

export default function AuthButtonClient({ session } : {session: Session | null}) {
  const supabase = createClientComponentClient();
  const router = useRouter();

  const handleSignOut = async () => {
    const { error } = await supabase.auth.signOut();
    router.refresh();

    if (error) {
      // eslint-disable-next-line no-console
      console.error('ERROR:', error);
    }
  }


  return session ? ( 
    <button className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded-full" type="button" onClick={handleSignOut} >
      Sign Out
    </button>
  ) : (
    <Link href="/login">
      
      <button className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded-full" type="button" onClick={handleSignOut} >
        Login
    </button>
    
    </Link>
  )
  
}

Server

import { createServerComponentClient } from "@supabase/auth-helpers-nextjs";
import { cookies } from "next/headers";
import AuthButtonClient from "./AuthButtonClient";

//for server component
export default async function AuthButtonServer(){
    const supabase = createServerComponentClient({cookies});

    const {data: {session}} = await supabase.auth.getSession();

    return <AuthButtonClient session={session}/>
}

And this is the navbar that I have done:

import AuthButtonServer from "@/app/auth/AuthButton/AuthButtonServer";
import Link from "next/link";
import NavbarComponent from "./NavBarComponent";

const NavBar = async ({ session} : {session: any}) => {  
    return ( 
        <div>
            {/* Links here */}

            {session ? <> 
            <NavbarComponent/>
            </>: <>You must login first</>}
            {/* Button for Login and Logout */}
            <AuthButtonServer/> 

        </div>
     );
}
 
export default NavBar;

And the NavBarComponent that I would like to put the Logout button at the right side of the navbar:

export default function NavbarComponent() {
    const pathname = usePathname()
  return (
    <Disclosure as="nav" className="bg-gray-800">
      {({ open }) => (
        <>
          <div className="mx-auto max-w-7xl px-2 sm:px-6 lg:px-8">
            <div className="relative flex h-16 items-center justify-between">
              <div className="absolute inset-y-0 left-0 flex items-center sm:hidden">
                {/* Mobile menu button*/}
                <Disclosure.Button className="relative inline-flex items-center justify-center rounded-md p-2 text-gray-400 hover:bg-gray-700 hover:text-white focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white">
                  <span className="absolute -inset-0.5" />
                  <span className="sr-only">Open main menu</span>
                  {open ? (
                    <XMarkIcon className="block h-6 w-6" aria-hidden="true" />
                  ) : (
                    <Bars3Icon className="block h-6 w-6" aria-hidden="true" />
                  )}
                </Disclosure.Button>
              </div>
              <div className="flex flex-1 items-center justify-center sm:items-stretch sm:justify-start">
                <div className="flex flex-shrink-0 items-center">
                  <img
                    className="h-8 w-auto"
                    src="https://tailwindui.com/img/logos/mark.svg?color=indigo&shade=500"
                    alt="Your Company"
                  />
                </div>
                   <div className="hidden sm:ml-6 sm:block">
                   <div className="flex space-x-4">
                     {navigation.map((item) => (
                       <Link
                         key={item.name}
                         href={item.href}
                         className={
                            item.href === pathname
                              ? 'bg-gray-900 text-white rounded-md px-3 py-2 text-sm font-medium'
                              : 'text-gray-300 hover:bg-gray-700 hover:text-white rounded-md px-3 py-2 text-sm font-medium'
                          }
                          aria-current={item.href === pathname ? 'page' : undefined}
                       >
                         {item.name}
                       </Link>
                     ))}
                   </div>
                 </div>
              </div>
              <div className="absolute inset-y-0 right-0 flex items-center pr-2 sm:static sm:inset-auto sm:ml-6 sm:pr-0">
                 <button
                  type="button"
                  className="relative rounded-full bg-gray-800 p-1 text-gray-400 hover:text-white focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-800"
                >
                  Logout
                </button>
                 </div>
            </div>
          </div>
          

          <Disclosure.Panel className="sm:hidden">
            <div className="space-y-1 px-2 pb-3 pt-2">
              {navigation.map((item) => (
                <Disclosure.Button
                  key={item.name}
                  as="a"
                  href={item.href}
                    className={classNames(
                    item.href === pathname ? 'bg-gray-900 text-white' : 'text-gray-300 hover:bg-gray-700 hover:text-white',
                    'block rounded-md px-3 py-2 text-base font-medium'
                  )}
                  aria-current={item.href === pathname ? 'page' : undefined}
                >
                  {item.name}
                </Disclosure.Button>
              ))}
            </div>
          </Disclosure.Panel>
        </>
      )}
    </Disclosure>
  )
}

Now, I am quite lost on how I can approach this. I cannot use the AuthButtonServer inside the NavBarComponent because this is a client component. Any suggestions would be greatly appreciated!


Solution

  • https://nextjs.org/docs/app/building-your-application/rendering/composition-patterns#supported-pattern-passing-server-components-to-client-components-as-props

    The following pattern is supported. You can pass Server Components as a prop to a Client Component.

    A common pattern is to use the React children prop to create a "slot" in your Client Component.

    In the example below, accepts a children prop:

    'use client'
     
    import { useState } from 'react'
     
    export default function ClientComponent({
      children,
    }: {
      children: React.ReactNode
    }) {
      const [count, setCount] = useState(0)
     
      return (
        <>
          <button onClick={() => setCount(count + 1)}>{count}</button>
          {children}
        </>
      )
    }
    

    ClientComponent doesn't know that children will eventually be filled in by the result of a Server Component. The only responsibility ClientComponent has is to decide where children will eventually be placed.

    In a parent Server Component, you can import both the ClientComponent and ServerComponent and pass ServerComponent as a child of ClientComponent

    // This pattern works:
    // You can pass a Server Component as a child or prop of a
    // Client Component.
    import ClientComponent from './client-component'
    import ServerComponent from './server-component'
     
    // Pages in Next.js are Server Components by default
    export default function Page() {
      return (
        <ClientComponent>
          <ServerComponent />
        </ClientComponent>
      )
    }