Next.js Discord

Discord Forum

Server component nested inside a client component

Unanswered
Ruyy posted this in #help-forum
Open in Discord
I'm working on a project using Next.js 14 and Supabase auth. In my project, I have a Navbar component that's a client-side component. However, one of the items in the Navbar is a 'signout' button that utilizes Supabase Server Actions.

I'm wondering how I can integrate this 'signout' button component within the Navbar.

15 Replies

@Ruyy I'm working on a project using Next.js 14 and Supabase auth. In my project, I have a Navbar component that's a client-side component. However, one of the items in the Navbar is a 'signout' button that utilizes Supabase Server Actions. I'm wondering how I can integrate this 'signout' button component within the Navbar.
SidebarNav.tsx
"use client";

// A bunch of imports
import { SignOutButton } from "../auth/SignOutButton";

const SidebarNavItems: ISidebarNavItem[] = [
  // All sidebar items
]

export function SidebarNav() {
  const [isExpanded, setIsExpanded] = useState(false);
  const { theme, setTheme } = useTheme();

  return (
    <aside className={cn("flex flex-col gap-5 w-80 h-screen sticky top-0 px-4 py-8", isExpanded ? 'w-80' : 'w-16 items-center')}>
      <div className="flex flex-col gap-6 px-4" >
        {isExpanded ? <Logo /> : <Aperture className="w-7 text-primary shrink-0" />}
        <Separator className="w-full" />
      </div>

      <nav className={cn("flex flex-col gap-4 grow justify-start", isExpanded ? "items-start" : "items-center")}>
        {SidebarNavItems.map(({ href, title, icon, disabled, external }) => (
          <SidebarNavItem href={href} key={title} title={title} icon={icon} disabled={disabled} external={external} isExpanded={isExpanded} />)
        )}
      </nav>

      <div className={cn("flex flex-col gap-4", isExpanded ? "items-start" : "items-center")}>
        // A bunch of buttons

        <SignOutButton>
          <Button
            variant="ghost"
            size={isExpanded ? "default" : "icon"}
            className={
              cn(
                "relative text-muted-foreground hover:text-secondary-foreground transition-colors",
                isExpanded ? "justify-start flex items-center gap-4 w-full" : "justify-center"
              )
            }
          >
            <figure className="shrink-0 w-5 flex items-center justify-center aspect-square">
              <LogOut />
            </figure>
            {isExpanded && <span>Sair da plataforma</span>}
          </Button>
        </SignOutButton>
      </div>
    </aside>
  );
};
@Ruyy I'm working on a project using Next.js 14 and Supabase auth. In my project, I have a Navbar component that's a client-side component. However, one of the items in the Navbar is a 'signout' button that utilizes Supabase Server Actions. I'm wondering how I can integrate this 'signout' button component within the Navbar.
SignOutButton.tsx
import { createClient } from '@/lib/database/server'
import { cookies } from 'next/headers'
import { redirect } from 'next/navigation'
import { ReactNode } from 'react'

interface SignOutButtonProps {
  children?: ReactNode
}

export async function SignOutButton({ children }: SignOutButtonProps) {
  const cookieStore = cookies()
  const supabase = createClient(cookieStore)

  const {
    data: { user },
  } = await supabase.auth.getUser()

  const signOut = async () => {
    'use server'

    const cookieStore = cookies()
    const supabase = createClient(cookieStore)
    await supabase.auth.signOut()
    return redirect('/auth/login')
  }

  if (!user) return null;

  return (
    <form action={signOut}>
      {children}
    </form>
  )
}
Pass the signout button to the sidebar through props, either as children or as it's own prop
@Plague Pass the signout button to the sidebar through props, either as children or as it's own prop
The issue is, the SignOutButton must receive either children or props.
Cuz it has conditionals depending on the Sidebar isExpanded state
  <SignOutButton>
    <Button
      variant="ghost"
      size={isExpanded ? "default" : "icon"}
      className={
        cn(
          "relative text-muted-foreground hover:text-secondary-foreground transition-colors",
          isExpanded ? "justify-start flex items-center gap-4 w-full" : "justify-center"
        )
      }
    >
      <figure className="shrink-0 w-5 flex items-center justify-center aspect-square">
        <LogOut />
      </figure>
      {isExpanded && <span>Sair da plataforma</span>}
    </Button>
  </SignOutButton>
@Ruyy The issue is, the SignOutButton must receive either children or props.
Then pass the button to the sidebar as a prop, render it in the sidebar, pass children to it there, I don't think you'll be able to pass props directly to it though, but, children should work.
Yeah literally exactly what you're doing, but, SignOutButton is a prop instead of an imported component
dashboard/layout.tsx
    <div className="flex w-full min-h-screen">
      <SidebarNav SignOutWrapper={<SignOutButton />} />
      <main className="flex-1">
        {children}
      </main>
    </div>`


SidebarNav.tsx
// a bunch of imports

interface SidebarNavProps {
  SignOutWrapper: ReactNode;
};

export function SidebarNav({ SignOutWrapper }: SidebarNavProps) {
  // some state functions

  return (
    <aside {...}>
      // a bunch of nav code

      <SignOutWrapper>
        <Button
          variant="ghost"
          size={isExpanded ? "default" : "icon"}
          className={
            cn(
              "relative text-muted-foreground hover:text-secondary-foreground transition-colors",
              isExpanded ? "justify-start flex items-center gap-4 w-full" : "justify-center"
            )
          }
        >
          <figure className="shrink-0 w-5 flex items-center justify-center aspect-square">
            <LogOut />
          </figure>
          {isExpanded && <span>Sair da plataforma</span>}
        </Button>
      </SignOutWrapper>
    </aside>
  )
}
@Ruyy I understand what you said, however, cant realy undertand the code... LOL
Ah okay, I misunderstood, what I'm talking about is not possible, you can't render the prop as a component so like: <SignOutWrapper /> you can only render it by saying { SignOutWrapper } so it is essentially just like the children prop except it has a unique name so you can seperate it from other children.

In your case then, if you want the children of SignOutButton to depend on state from the Sidebar, you're going to have to lift that state up into context, consume it in a client component that wraps your button, then render that client component inside your SignOutButton and then pass the SignOutButton to your sidebar.

I am not seeing another way to get the children of a server component those state values without using context and wrapping them in a client component.
This is very common in the app directory for navigation, some parts don't need to be client components or in your case can't, so context for the state is ideal.