3

I have an account page, which the user ends up after signing up. As soon as they hit this page, I would like to create a profile row with a 1-1 mapping to a (Supabase) users table.

If this would be a client side page, I would do something like this:

const ProfilePage: FC<{ userId: string }> = ({ userId }) => {
  const [profile, setProfile] = useState<Profile | null>(null);

  useEffect(() => {
    getProfile().then(profile => {
      if (profile) {
        setProfile(profile)
      } else {
        createProfile({ user_id: userId, name: email.split("@")[0], avatar: null })
          .then(setProfile)
      }
    })
  }, [])

  return profile ? (
    <div>Welcome, {profile.name}</div>
  ) : (
    <div>Loading your profile / setting up your profile ...</div>
  )

}

Now I want to do this in a server component. If I understand correctly, useEffect() is a client-side hook. How do I check if something exists, run something, and make sure to re-render the page as soon as the profile has been created?

Yilmaz
  • 35,338
  • 10
  • 157
  • 202
Sventies
  • 2,314
  • 1
  • 28
  • 44
  • I don't have specific experience with this, but sounds like a use-case for [hydrateRoot](https://react.dev/reference/react-dom/client/hydrateRoot) – Dylan Jul 07 '23 at 22:26

2 Answers2

1
useEffect(() => {
    getProfile().then(profile => {
      if (profile) {
        setProfile(profile)
      } else {
        createProfile({ user_id: userId, name: email.split("@")[0], avatar: null })
          .then(setProfile)
      }
    })
  }, [])

the code inside the useEffect is related to the supabase user. you can use createServerComponentClient on the server.

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


const getProfileByIdOrCreateProfileById = async (id: string): Promise<Profile> => {
  const supabase = createServerComponentClient({
    cookies: cookies,
  });

  const { data, error } = await supabase
    .from('users')
    .select('*')
    .eq('id', id)
    .single();

  if (error) {
    console.log(error.message);
  }
  if (data){
    return (data as any) || [];
  }
  // your original post uses `email`. you did not post the related code where email comes from 
  const response= supabase.from('users').insert({ user_id: id, name: email.split("@")[0], avatar: null })
  // using response return the correct value
  return {response}
};

I showed the getting or creating profile in the same function. But actually using server actions would be much better to insert the profile. Example from here

import { cookies } from 'next/headers'
import { createServerActionClient } from '@supabase/auth-helpers-nextjs'
import { revalidatePath } from 'next/cache'

export default async function NewTodo() {
  const addTodo = async (formData) => {
    'use server'

    const title = formData.get('title')
    const supabase = createServerActionClient({ cookies })
    await supabase.from('todos').insert({ title })
    revalidatePath('/')
  }

  return (
    <form action={addTodo}>
      // in your post you are using email somehow
      <input name="email" />
    </form>
  )
}
Yilmaz
  • 35,338
  • 10
  • 157
  • 202
1

In this case I think just a boolean reference indicating the first render is simplest.

const ProfilePage = ({ userId }) => {
  const [profile, setProfile] = useState(null);
  const firstRender = useRef(true);
  if (firstRender.current) {
    firstRender.current = false;
    getProfile(userId).then((profile) => {
      if (profile) {
        setProfile(profile);
      } else {
        createProfile({
          user_id: userId,
          name: email.split('@')[0],
          avatar: null,
        }).then(setProfile);
      }
    });
  }

  return profile ? (
    <div>Welcome, {profile.name}</div>
  ) : (
    <div>Loading your profile / setting up your profile ...</div>
  );
};

Demo: https://stackblitz.com/edit/stackblitz-starters-d3z6hx?file=pages%2Fprofile.tsx

Chris Hamilton
  • 9,252
  • 1
  • 9
  • 26