2

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?

  • I don’t want to add a new document for each file/image, its only one document containing a group of files if the user decides that. – Guillermina Martinez Jul 23 '21 at 08:15
  • You mean an array of urls? – Dharmaraj Jul 23 '21 at 08:17
  • A document inside a collection. That document is an object (I guess) containing an array of images inside. Isn’t it like that how it works? – Guillermina Martinez Jul 23 '21 at 08:20
  • You can store array of uploaded images. Just update the relevant document with the fileUrls array – Dharmaraj Jul 23 '21 at 08:21
  • I am not sure this solution is working or I am missing something that perhaps you thought I would realise. From the start I am already getting an error e.target.files.map is not a function. – Guillermina Martinez Jul 23 '21 at 08:24
  • My bad, try `Array.from(e.target.files).map(....` – Dharmaraj Jul 23 '21 at 08:25
  • Thank you for that. I am getting the images in storage but not the document with [...data] in firestore. Should I chain .doc().set({})? – Guillermina Martinez Jul 23 '21 at 08:32
  • After fileUrls line, `await firebase.firestore().collection("colName").add({fileUrls})`.. and add any other data in add if needed. – Dharmaraj Jul 23 '21 at 08:38
  • I cannot pass more data after fileUrls line because that’s inside the onChange, and my data is inside the onSubmit. Therefore, a new collection is created with the urls, but I don’t get to send [...data] to 'listings' collection – Guillermina Martinez Jul 23 '21 at 08:51
  • What's name of new collection? Can't you set fileUrls in state and use it in onSubmit... ? Do you got [discord](https://discord.firebase.me)? – Dharmaraj Jul 23 '21 at 08:54
  • Thank you so much, the credit is totally yours. If you agree, I can edit the post showing the final solution. Will that jeopardise your credit or is it fine? Edited: it is okey, let’s show only yours ;) – Guillermina Martinez Jul 23 '21 at 09:04
  • No it's fine you can edit it and show latest answer. I can't edit it atm :) – Dharmaraj Jul 23 '21 at 09:27

2 Answers2

1

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
Dharmaraj
  • 47,845
  • 8
  • 52
  • 84
0

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)

    }
  };