Upload multiple files and query Firestore DB - ReactJS

Viewed 526

I have been reading quite a lot posts like this one How to upload multiple files to Firebase? about this question before and I tried different options but can’t get my result. I am using React Hook Form for an insanely huge form that now I have to add an input more (to upload files), and need help doing everything at once.

My code to update one file:

  const onChangeFile = async (e: any) => {
    const file = e.target.files[0];
    const storageRef = app.storage().ref();
    const fileRef = storageRef.child(file.name);
    await fileRef.put(file);
    setFileUrl(await fileRef.getDownloadURL());
  };

  const onSubmit = async (data: any) => {
    
    try {
      await db
        .collection('listings')
        .doc()
        .set({ ...data, created: new Date(), uid: currentUser.uid, file: fileUrl });
      reset();

    } catch (error) {

    }
  };

So, as you can see, uploading only one file is quite straightforward, but a different story is when uploading many. Thing is, I would need more functions inside my onSubmit, which is itself an async funcion, so that limits the amount of things I can do inside of it. Does anyone have an simple workaround?

2 Answers

If e.target.files has multiple files then you can push the upload operations to an array and then upload them using Promise.all():

const onChangeFile = async (e: any) => {
  const storageRef = app.storage().ref();
  const fileUploads = e.target.files.map(file => storageRef.child(file.name).put(file));
      
  const fileURLReqs = (await Promise.all(fileUploads)).map(f => f.ref.getDownloadURL())
  const fileUrls = await Promise.all(fileURLReqs)
  // setFileUrl(await fileRef.getDownloadURL());
};

fileUrls will be an array of URLs of all the uploads images which onSubmit can be uploaded to Firestore if needed.

I'm not sure if you are adding a new document for each image but if you are, try this:

// in onSubmit
const listingsCol = db.collection("listings")
const files = [] // get that fileUrls array from your state
const updates = files.map(file => listingsCol.add({...data, file}))
await Promise.all(updates)
// this will add a new document for each new file uploaded

Thanks to @Dharmaraj, I have arrived to a simple working version of this popular question, I think he has given a very clear and straightforward solution for it and the credit is totally his.

Nonetheless, my implementation could have weaknesses even though its working (so far), so I would like to post it, you can use it or point out possible red flags.

const Form: React.FC = () => {
  const [filesUrl, setFilesUrl] = useState<any[]>([]);
  const { register, handleSubmit, reset } = useForm({ defaultValues });

  const onChangeFile = async (e: any) => {
    const storageRef = app.storage().ref();
    const fileUploads = Array.from(e.target.files).map((file: any) => storageRef.child(file.name).put(file));
    const fileURLReqs = (await Promise.all(fileUploads)).map((f: any) => f.ref.getDownloadURL());
    const fileUrls = await Promise.all(fileURLReqs);
    setFilesUrl(fileUrls);
  };

  const onSubmit = async (data: any) => {

    try {
      await db
        .collection('collName')
        .doc()
        .set({ ...data, created: new Date(), uid: currentUser.uid, file: filesUrl });
      reset();

    } catch (error) {
    console.log(error)

    }
  };
Related