Next.js Discord

Discord Forum

Form Mutating and Validation

Answered
Little yellow ant posted this in #help-forum
Open in Discord
Little yellow antOP
I'm trying to use server actions to obtain an array of image urls transformed by firebase storage, but the data received by the createImage() action are the files and not the transformed URLs, can anyone help?

'use client'

import { useFormState } from 'react-dom';
import { getDownloadURL, ref, uploadBytesResumable } from 'firebase/storage';

import { createImage} from '@/actions/images';
import { storage } from '@/lib/firebase';

export default function Form() {
  const [imageURLs, setImageURLs] = useState<string[]>([]);
  const initialState = { message: null, errors: {} };
  const [state, dispatch] = useFormState(createImage, initialState);
  
  const handleImages = async (e) => {
    const images = Array.from(e.target.files);
    
    images.forEach((image) => {
      const imageId = crypto.randomUUID();
      const imageRef = ref(storage, `uploads/images/${image.name}-${imageId}`;
      const uploadTask = uploadBytesResumable(imageRef, image.file);

      uploadTask.on("state_changed", () => console.log, (error) => console.log(error), async () => {
        const downloadURL = await getDownloadURL(uploadTask.snapshot.ref)
        setImageURLs(prevURLs => [...prevURLs, downloadURL]);
      })
    })
  }

  return (
    <form action={dispatch}>
      <input 
        id="imgs" 
        name="images" 
        type="file" 
        accept="image/*" 
        defaultValue={imageURLs} 
        onChange={handleImages} 
        multiple 
      />
      {state.erros?.images 
        ? (
            <div id="images-error" aria-live"polite">
              {state.erros.images.map((error: string) => 
                <span key={error} className="text-red-500 text-sm">{error}</span>)}
            </div>
          )
        : null
      }
      <Button type="submit">Submit</Button>
    </form>
  )

import { z } from 'zod';
import { revalidatePath } from 'next/cache';
import { redirect } from 'next/navigation';
import { sql } from '@vercel/postgres';

export type State = {
  errors?: {
    images?: string[];
  };
  message?: string | null;
};

const FormSchema = z.object({
  id: z.string(),
  images: z.array(z.string()),
  createdAt: z.string(),
})

const CreateImage = FormSchema.omit({ id: true, createdAt: true });

export async function createImage(prevState: State, formData: FormData) {
  const validatedFields = CreateProperty.safeParse({
    images: formData.getAll('images'),
  });

  if (!validatedFields.success) {
    return {
      errors: validatedFields.error.flatten().fieldErrors,
      message: 'Fill the necessary fields.'
    };
  }

  const { images } = validatedFields.data
  const date = new Date().toISOString().split('T')[0];

try {
    await sql`
      INSERT INTO images (images, created_at) VALUES (${images}, ${date})
      ON CONFLICT (id) DO NOTHING;
    `

    console.log('Image sucessfully created');
  } catch (error) {
    console.error('Something went wrong', error);
    return {
      message: 'Database Error: Failed to Create Image.',
    };
  }
  
  revalidatePath('/dashboard/images');
  redirect('/dashboard/images');  
}
Answered by Ray
{imageURLs.map(url => (
  <>
    <input key={url} type="hidden" name="images" value={url} />
  </>
)
View full answer

5 Replies

Little yellow antOP
Is possible change the value get in server action?
map the imageURLs to render a list of input
{imageURLs.map(url => (
  <>
    <input key={url} type="hidden" name="images" value={url} />
  </>
)
Answer
inside the form
Little yellow antOP
thank you so much @Ray