Tsabary
Tsabary

Reputation: 3928

Firebase upload function works differently everytime

I have a form to upload a new item, that has a few fields and an image. The way I've built the function is that it first sets the new document in Firestore, then it uploads the image, and then it resets the form.

The problem is that it doesn't always work. Sometimes the image gets uploaded and sometimes it isn't even if the form was reset (the reset is conditioned with the image upload).

It's not consistent so I can't figure out what exactly is happening.

This is the upload function:

export const newItem = (values, image, setValues) => () => {
  const newDoc = db.collection("items").doc();

  newDoc.set({ ...values, id: newDoc.id }).then(() => {
    storageRef
      .child(`images/items/${newDoc.id}`)
      .put(image)
      .then(result => {
        console.log(result);
        setValues({});
      });
  });
}; 

I call it is as follows:

newItem({ ...values, is_public }, imageObj, setValues);

Then I have this cloud function that addes the url for the newly uploaded file to the new document (but I don't think the issue is there, because when I say the image wasn't uploaded, then I mean I don't even see it in storage):

exports.writeFileToDatabase = functions.storage.object().onFinalize(object => {
  const bucket = defaultStorage.bucket();
  const path = object.name as string;
  const file = bucket.file(path);

  return file
    .getSignedUrl({
      action: "read",
      expires: "03-17-2025"
    })
    .then(results => {
      const url = results[0];
      const silcedPath = path.split("/", 3);

      switch (silcedPath[1]) {
        case "user-avatars":
          return db
            .collection("users")
            .doc(silcedPath[2])
            .set({ avatar: url }, { merge: true });

        case "items":
          return db
            .collection("items")
            .doc(silcedPath[2])
            .set({ image: url }, { merge: true });

        default:
          return null;
      }
    });
});

EDIT:

this is how I choose the file:

  <input
    id="image"
    className="new-item__upload"
    type="file"
    onChange={handleImageChange}
  />

Then this is handleImageChange:

  const handleImageChange = e => {
    if (e.target.files[0]) {
      const image = e.target.files[0];
      setSelectedImage(URL.createObjectURL(image));
      setImageObj(image); // This is what end up being given to the function to upload
    }
  };

Upvotes: 0

Views: 101

Answers (1)

Renaud Tarnec
Renaud Tarnec

Reputation: 83058

You need to correctly chain the promises returned by the Firebase asynchronous operations (set() and put()) as follows:

export const newItem = (values, image, setValues) => () => {
  const newDoc = db.collection("items").doc();

  newDoc.set({ ...values, id: newDoc.id })
   .then(() => {
     return storageRef         //Here return the Promise returned by the put() method
      .child(`images/items/${newDoc.id}`)
      .put(image);
   })
   .then(snapshot => {   
       console.log(snapshot);
       setValues({});
   })
   .catch(e => {
      console.error(e.message); 
      //You should throw an error here, see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Control_flow_and_error_handling
   });

};

It is also recommended to add a catch() method call at the end of your Promises chain, in order to get more details in case of error.

Upvotes: 1

Related Questions