workos / authkit-nextjs

The WorkOS library for Next.js provides convenient helpers for authentication and session management using WorkOS & AuthKit with Next.js.
MIT License
58 stars 14 forks source link

sign out not working #41

Closed kenhuang1964 closed 4 months ago

kenhuang1964 commented 5 months ago

Can I use sign out in a client component? I followed all the directions except I am using the sign out function in a client component and it doesn't work. I am getting this error:

Error: 
  × It is not allowed to define inline "use server" annotated Server Actions in Client Components.
  │ To use Server Actions in a Client Component, you can either export them from a separate file with "use server" at the top, or pass them down through props from a Server Component.
  │ 
  │ Read more: https://nextjs.org/docs/app/api-reference/functions/server-actions#with-client-components
  │ 
     ╭─[/Users/kenhuangsy/Desktop/Coding/Web Development/athenian-creators-hub-web-nextjs/components/all-pages/Navbar.tsx:100:1]
 100 │                 <UserButton />
 101 │                 {user ? (
 102 │                   <form
 103 │ ╭─▶                 action={async () => {
 104 │ │                     'use server';
 105 │ │                     await signOut();
 106 │ ╰─▶                 }}
 107 │                   >
 108 │                     <Button className="mr-4 bg-[#60A5Fa]" type="submit">
 109 │                       Sign Out
     ╰────

I tried creating a new typescript file with "use server" at the top and imported the function from that file to the client component but it still doesn't work. Can someone please help me?

Here is my current code which requires "use client" at the top:


"use client";

import { UserButton } from "@clerk/nextjs";
import {
  NavigationMenu,
  NavigationMenuContent,
  NavigationMenuItem,
  NavigationMenuLink,
  NavigationMenuList,
  NavigationMenuTrigger,
} from "@/components/ui/navigation-menu";
import { cn } from "@/lib/utils";
import React from "react";
import Link from "next/link";
import { Button } from "@/components/ui/button";
import {
  getSignInUrl,
  getSignUpUrl,
  getUser,
  signOut,
} from "@workos-inc/authkit-nextjs";

const components: { title: string; href: string; description: string }[] = [
  {
    title: "Technology",
    href: "/topics/technology",
    description: "Share your cool programming or engineering projects!",
  },
  {
    title: "Art",
    href: "/topics/art",
    description: "Show off your art, music, or other creative projects!",
  },
  {
    title: "Theater and Filmmaking",
    href: "/topics/theater-and-filmmaking",
    description: "Share and watch your films or plays!",
  },
  {
    title: "March Term",
    href: "/topics/march-term",
    description: "Share your projects or cool experiences from March Term!",
  },
  {
    title: "Sports",
    href: "/topics/sports",
    description: "Sport news.",
  },
  {
    title: "Outdoors",
    href: "/topics/outdoors",
    description: "Nature and outdoor activities.",
  },
  {
    title: "Other",
    href: "/topics/other",
    description: "Anything that doesn't fit in the other categories!",
  },
];

export const Navbar = async () => {
  const { user } = await getUser();
  const signInUrl = await getSignInUrl();
  const signUpUrl = await getSignUpUrl();

  return (
    <nav className="p-5 flex justify-around w-full shadow-md bg-muted">
      <Link href="/">
        <span className="self-center text-2xl font-semibold whitespace-nowrap dark:text-white">
          Athenian Creators Hub
        </span>
      </Link>
      <NavigationMenu>
        <NavigationMenuList>
          <div className="flex items-center">
            <NavigationMenuItem>
              <Link href="/create-post">
                <Button className="mr-4 bg-[#60A5Fa]">Share a Post</Button>
              </Link>
            </NavigationMenuItem>

            <NavigationMenuItem>
              <NavigationMenuTrigger>Navigation</NavigationMenuTrigger>
              <NavigationMenuContent>
                <ul className="grid w-[400px] gap-3 p-4 md:w-[500px] md:grid-cols-2 lg:w-[600px]">
                  {components.map((component) => (
                    <ListItem
                      key={component.title}
                      title={component.title}
                      href={component.href}
                    >
                      {component.description}
                    </ListItem>
                  ))}
                </ul>
              </NavigationMenuContent>
            </NavigationMenuItem>
          </div>
          <NavigationMenuItem>
            <UserButton />
            {user ? (
              <form
                action={async () => {
                  'use server';
                  await signOut();
                }}
              >
                <Button className="mr-4 bg-[#60A5Fa]" type="submit">
                  Sign Out
                </Button>
              </form>
            ) : (
              <Link href={signUpUrl}>
                <Button className="mr-4 bg-[#60A5Fa]">Sign In</Button>
              </Link>
            )}
          </NavigationMenuItem>
        </NavigationMenuList>
      </NavigationMenu>
    </nav>
  );
};

const ListItem = React.forwardRef<
  React.ElementRef<"a">,
  React.ComponentPropsWithoutRef<"a">
>(({ className, title, children, ...props }, ref) => {
  return (
    <li>
      <NavigationMenuLink asChild>
        <a
          ref={ref}
          className={cn(
            "block select-none space-y-1 rounded-md p-3 leading-none no-underline outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground",
            className,
          )}
          {...props}
        >
          <div className="text-sm font-bold leading-none">{title}</div>
          <p className="line-clamp-2 text-sm leading-snug text-muted-foreground">
            {children}
          </p>
        </a>
      </NavigationMenuLink>
    </li>
  );
});
ListItem.displayName = "ListItem";
lucasmotta commented 5 months ago

Hey @kenhuang1964 - with the code example that you have shared with us, it does not look like your component needs to be a client component – can you try removing the use client from the top and let us know if that works?

Also it looks like your NavBar component is a Server Component (it's marked as async), so that could be potentially an issue if you are marking the component as Client Component.

lucasmotta commented 5 months ago

And here's a contrived example on how to call the server action from a client component:

// actions.ts
'use server';

import { signOut } from "@workos-inc/authkit-nextjs"; 

export const signOutAction = () => signOut();
// client-component.tsx
'use client';

import { signOutAction } from './actions.ts';

export function ClientComponent() {
  return (
    <form action={signOutAction}>
      <button type="submit">Sign out</button>
    </form>
  );
}
kenhuang1964 commented 4 months ago

Hey @lucasmotta, thank you for your help! I ended up still having to use a client component and the reason that I had async there was because I needed to use

  const { user } = await getUser();
  const signInUrl = await getSignInUrl();
  const signUpUrl = await getSignUpUrl();

I created a new file called authUtils.ts:

  "use server";

import {
  getSignInUrl,
  getSignUpUrl,
  getUser,
  signOut,
} from "@workos-inc/authkit-nextjs";

export async function getUserData() {
  const { user } = await getUser();
  const signInUrl = await getSignInUrl();
  const signUpUrl = await getSignUpUrl();

  return {
    user,
    signInUrl,
    signUpUrl,
  };
}

export const signOutAction = () => signOut;

and updated my client component code accordingly:

"use client";

import { UserButton } from "@clerk/nextjs";
import {
  NavigationMenu,
  NavigationMenuContent,
  NavigationMenuItem,
  NavigationMenuLink,
  NavigationMenuList,
  NavigationMenuTrigger,
} from "@/components/ui/navigation-menu";
import { cn } from "@/lib/utils";
import React, { use, useEffect, useState } from "react";
import Link from "next/link";
import { Button } from "@/components/ui/button";
import { getUserData, signOutAction } from "@/utils/authUtils";

const components: { title: string; href: string; description: string }[] = [
  {
    title: "Technology",
    href: "/topics/technology",
    description: "Share your cool programming or engineering projects!",
  },
  {
    title: "Art",
    href: "/topics/art",
    description: "Show off your art, music, or other creative projects!",
  },
  {
    title: "Theater and Filmmaking",
    href: "/topics/theater-and-filmmaking",
    description: "Share and watch your films or plays!",
  },
  {
    title: "March Term",
    href: "/topics/march-term",
    description: "Share your projects or cool experiences from March Term!",
  },
  {
    title: "Sports",
    href: "/topics/sports",
    description: "Sport news.",
  },
  {
    title: "Outdoors",
    href: "/topics/outdoors",
    description: "Nature and outdoor activities.",
  },
  {
    title: "Other",
    href: "/topics/other",
    description: "Anything that doesn't fit in the other categories!",
  },
];

export const Navbar = () => {
  const [user, setUser] = useState<any>(null);
  const [signInUrl, setSignInUrl] = useState<string>("");
  const [signUpUrl, setSignUpUrl] = useState<string>("");

  useEffect(() => {
    const fetchUserData = async () => {
      const { user, signInUrl, signUpUrl } = await getUserData();
      setUser(user);
      setSignInUrl(signInUrl);
      setSignUpUrl(signUpUrl);
      console.log("Sign In URL:", signInUrl);
      console.log("Sign Up URL:", signUpUrl);
      console.log("User:", user);
    };
    fetchUserData();
  }, [signInUrl, signUpUrl, user]);

  return (
    <nav className="p-5 flex justify-around w-full shadow-md bg-muted">
      <Link href="/">
        <span className="self-center text-2xl font-semibold whitespace-nowrap dark:text-white">
          Athenian Creators Hub
        </span>
      </Link>
      <NavigationMenu>
        <NavigationMenuList>
          <div className="flex items-center">
            <NavigationMenuItem>
              <Link href="/create-post">
                <Button className="mr-4 bg-[#60A5Fa]">Share a Post</Button>
              </Link>
            </NavigationMenuItem>

            <NavigationMenuItem>
              <NavigationMenuTrigger>Navigation</NavigationMenuTrigger>
              <NavigationMenuContent>
                <ul className="grid w-[400px] gap-3 p-4 md:w-[500px] md:grid-cols-2 lg:w-[600px]">
                  {components.map((component) => (
                    <ListItem
                      key={component.title}
                      title={component.title}
                      href={component.href}
                    >
                      {component.description}
                    </ListItem>
                  ))}
                </ul>
              </NavigationMenuContent>
            </NavigationMenuItem>
          </div>
          <NavigationMenuItem>
            <UserButton />
            {user ? (
              <form action={signOutAction}>
                <Button className="mr-4 bg-[#60A5Fa]" type="submit">
                  Sign Out
                </Button>
              </form>
            ) : (
              <Link href={signInUrl}>
                <Button className="mr-4 bg-[#60A5Fa]">Sign In</Button>
              </Link>
            )}
          </NavigationMenuItem>
        </NavigationMenuList>
      </NavigationMenu>
    </nav>
  );
};

const ListItem = React.forwardRef<
  React.ElementRef<"a">,
  React.ComponentPropsWithoutRef<"a">
>(({ className, title, children, ...props }, ref) => {
  return (
    <li>
      <NavigationMenuLink asChild>
        <a
          ref={ref}
          className={cn(
            "block select-none space-y-1 rounded-md p-3 leading-none no-underline outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground",
            className,
          )}
          {...props}
        >
          <div className="text-sm font-bold leading-none">{title}</div>
          <p className="line-clamp-2 text-sm leading-snug text-muted-foreground">
            {children}
          </p>
        </a>
      </NavigationMenuLink>
    </li>
  );
});
ListItem.displayName = "ListItem";

I am able to get the sign in and sign up working, but sign out is still not working. I am facing this error and I'm not sure how to resolve it because I have "use server" at the top of the file for authUtils.ts. This is the error:

 ⨯ Error: Functions cannot be passed directly to Client Components unless you explicitly expose it by marking it with "use server".
  {: function}
     ^^^^^^^^
    at stringify (<anonymous>)
lucasmotta commented 4 months ago

I think it's closer now - in your signOutAction you need to invoke the signOut():

export const signOutAction = () => signOut();

And I also created a simplified demo showing how to call a server action from a client component (I also moved the fetching of the user data to the server component): https://codesandbox.io/p/devbox/infallible-danilo-kzjvm4

Let me know if that helps.

kenhuang1964 commented 4 months ago

I think it's closer now - in your signOutAction you need to invoke the signOut():

export const signOutAction = () => signOut();

And I also created a simplified demo showing how to call a server action from a client component (I also moved the fetching of the user data to the server component): https://codesandbox.io/p/devbox/infallible-danilo-kzjvm4

Let me know if that helps.

My glorious king it's working ❤️ I love u