skip to Main Content

So I’m a little confused on how client components work. In my project I am trying to create an image uploader, and I need to pull the userId from supabase, the supabase server function, and I need to upload the image to supabase storage using the "userId/filename. So I am trying to create a button or use and input with the onChange prop I’m not able to add the function into the button because it clashes with the server side function. Here’s my code:

import React from 'react';
import { Label } from './ui/label';
import { Input } from './ui/input';
import { createClient } from '@/utils/supabase/server';
import { Button } from './ui/button';
import { useToast } from './ui/use-toast';
import { TestButton } from './button';

const UploadImage = () => {
  // const { toast } = useToast();
  async function handleUpload (e: React.ChangeEvent<HTMLInputElement>) {
    // alert('Uploading image');

    const supabase = createClient();

    const { data } = await supabase.auth.getUser();

    alert(data.user?.id);

    let file;

    if (e.target.files) {
      // alert(`e:${e}`);
      file = e.target.files[0];
      // alert(`file: ${file.name}`);
    }

    const userId = await supabase.auth.getUser();
    const id = userId.data.user?.id;

    alert(userId.data.user);

    try {
      const { data, error } = await supabase.storage
        .from('avatars')
        .upload('5ff1e6f3-6974-4398-b329-14d737571129/test', file as File, {
          // cacheControl: '3600',
          upsert: true,
        });

      // const { data, error } = await supabase.storage
      // .from('avatars')
      // .upload(
      //   '5ff1e6f3-6974-4398-b329-14d737571129/' + file!.name,
      //   file as File,
      //   {
      //     cacheControl: '3600',
      //     upsert: true,
      //   }
      // );

      // alert(data);

      if (data) {
        alert(data);
        alert('successfully uploaded');
        // toast({
        //   title: 'Image Uploaded',
        //   description: 'Your image was successfully uploaded',
        // });
      } else if (error) {
        // alert(error.message);
      }
    } catch (e) {
      // alert(e);
    }
  };
  return (
    <div>
      <Label htmlFor="picture">Picture</Label>
      <Input
        id="file_input"
        type="file"
        accept="image/*"
        onChange={e => {
          handleUpload(e);
        }}
      />
    </div>
  );
};

export default UploadImage;


TLDR:
How do I create a client component that needs a server-side functionality, or what’s the best way to go about this situation.

Im sorry if this is vague, I don’t fully understand what’s going on, so bear with me. Thanks

2

Answers


  1. Chosen as BEST ANSWER

    Here's how I completed my code, just in case anyone needs it:

    // utils/server/upload.ts
    
    'use server';
    
    import {createClient} from '../supabase/server';
    
    export default async function supabaseUpload(data: FormData): Promise<boolean> {
      const supabase = createClient();
      const userId = (await createClient().auth.getUser()).data.user?.id
    
      console.log(`userId: ${userId}`)
      try {
        const file = data.get('file') as File;
        const imageExtension = file.name.split('.').pop()
        if (file)
          await supabase.storage.from('avatars').upload(`${userId}/profile.${imageExtension}`, file as File, {
            cacheControl: '3600',
            upsert: true,
            contentType: `image/${imageExtension}`
          });
        return true;
      } catch (error) {
        console.log(error);
        return false;
      }
    }
    
    

    Here's where the function is used for the client component

    // components/Uploader.tsx
    
    "use client"
    
    import React, { useCallback } from 'react';
    import { Label } from './ui/label';
    import { Input } from './ui/input';
    import { createClient } from '@/utils/supabase/server';
    import supabaseUpload from '@/utils/server/upload';
    
    const UploadImage = () => {
      const handleUpload = useCallback(
        async (e: React.ChangeEvent<HTMLInputElement>) => {
    
          try {
            const file = e.target.files?.[0];
            if (!file) throw new Error('No files selected');
    
            const formData = new FormData();
            formData.append('file', file);
    
            const success = await supabaseUpload(formData);
    
            alert(success)
    
          } catch (e) {
            alert(e);
          }
        },
        []
      );
      return (
        <div>
          <Label htmlFor="picture">Picture</Label>
          <Input
            id="file_input"
            type="file"
            accept="image/*"
            onChange={handleUpload}
          />
        </div>
      );
    };
    
    export default UploadImage;
    
    

  2. Create a file called server/upload.ts and export a supabaseUpload method from it. This will be a server action that receives your file via FormData, inside the function you can fetch the user securely on the server side and upload the file.

    "use server";
    
    // server/upload.ts
    export default async function supabaseUpload(data: FormData): Promise<boolean> {
      try {
        const file = formData.get("file") as File;
        // insert your upload logic here ..
        return trie;
      } catch (error) {
        console.log(error);
        return false;
      }
    }
    

    On the client side, inside your handleUpload function you create a new FormData object and call the server action to upload the file.

    const handleUpload = useCallback(async (e: React.ChangeEvent<HTMLInputElement>) => {
      try {
        const file = e.target.files?.[0];
        if (!file) throw new Error("No files selected");
    
        const formData = new FormData();
        formData.append("file", file);
    
        const success = await supabaseUpload(formData);
    
        // do whatever with the success value
      } catch (error) {
        console.error(error);
      }
    }, []);
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search