Home > Enterprise >  Upload in Firebase Storage and fetch link to add it in Realtime Database
Upload in Firebase Storage and fetch link to add it in Realtime Database

Time:04-08

I'm using Firebase Realtime Database and Firebase Storage in this application, the goal is to upload in Firebase Storage the images in the pictures array, and then get the link of the Firebase Storage to that image and add it in the object which will be pushed in imagesUriArray and added to Realtime Database. The problem is that when I press addItem it successfully update the id, but the images parameter remains empty. And in fact, imagesUriArray remains empty unless I refresh the screen.

 export default function NewItem({ route, navigation }) {

    const [pictures, setPictures] = useState([]);
    const [imagesUriArray, setImageUriArray] = useState([]);

    useEffect(() => {
        navigation.setOptions({
            headerRight: () => (
                <TouchableOpacity onPress={addItem}>
                    <Text style={{fontWeight:'bold'}}>ADD ITEM</Text>
                </TouchableOpacity>
            )
        })
    })
    

    const addItem = async () => {

            uploadImages()

            const changes = ref(db, path)
                get(changes).then(async (snapshot) => {
                    if (snapshot.val().data !== undefined) {
                        const fetchedArray = snapshot.val().data

                        let array = fetchedArray;

                        let object = {
                            "id": `${Math.random()}`,
                            "images": imagesUriArray,
                        }

                        array.push(object)
                    
                        update(changes, {
                            data: array
                        })
                    
                    }
                })
        }
    }

    const uploadImages = () => {

        const metadata = {
            contentType: 'image/jpeg',
        };


        pictures.forEach( async (obj) => {
            const id = obj.id
            const uri = obj.uri

            const response = await fetch(uri);
            const blob = await response.blob();
            var ref = storageUpload(storage, path)
            await uploadBytes(ref, blob, metadata)

            await getDownloadURL(ref)
            .then((metadata) => {
                let array = imagesUriArray
                    
                let object = {
                    "id": id,
                    "uri": metadata
                }

                array.push(object)
                setImageUriArray(array)
            })
            .catch((error) => {
                console.log(error)
            });
        })
    }

  return(
     ..............
  )
}

CodePudding user response:

Issue

This appears to be a state mutation in the uploadImages callback. A reference to the imagesUriArray state is cached locally, mutated (i.e. direct push into array), and then the same reference is saved back into state. This doesn't trigger react to rerender with any updated state value.

const uploadImages = () => {
  ...
  
  pictures.forEach( async (obj) => {
    const id = obj.id
    const uri = obj.uri

    const response = await fetch(uri);
    const blob = await response.blob();
    var ref = storageUpload(storage, path)
    await uploadBytes(ref, blob, metadata)

    await getDownloadURL(ref)
      .then((metadata) => {
        let array = imagesUriArray // <-- reference to state
                    
        let object = {
          "id": id,
          "uri": metadata
        }

        array.push(object)         // <-- state mutation
        setImageUriArray(array)    // <-- same state reference
      })
      .catch((error) => {
        console.log(error)
      });
  })
};

Solution

Use a functional state update to update from the previous state. Create a shallow copy of the previous state and append the new data.

const uploadImages = () => {
  ...
  
  pictures.forEach( async (obj) => {
    const { id, uri } = obj;

    const response = await fetch(uri);
    const blob = await response.blob();
    const ref = storageUpload(storage, path);
    await uploadBytes(ref, blob, metadata);

    await getDownloadURL(ref)
      .then((uri) => {
        setImageUriArray(imagesUriArray => [
          ... imagesUriArray, // <-- shallow copy
          { id, uri },        // <-- append new object
        ]);
      })
      .catch(console.log);
  })
};

Update

uploadImages needs to return a Promise so that addItem can wait for it to complete its asynchronous code. addItem also needs to access the updated imagesUriArray that uploadImages updates.

Map the pictures array to an array of Promises (i.e. an async fetchImageUri function) that eventually returns the object with id and new uri properties.

const uploadImages = () => {
  ...

  const fetchImageUri = async ({ id, uri }) => {
    try {
      const response = await fetch(uri);
      const blob = await response.blob();
      const ref = storageUpload(storage, path);
      await uploadBytes(ref, blob, metadata);

      const newUri = await getDownloadURL(ref);
      return { id, uri: newUrl };
    } catch(error) {
      console.log(error);
    }
  }

  return Promise.all(pictures.map(fetchImageUri));
};

Update addItem to wait for the resolved array of Promises that contain the uploaded image data. Enqueue the imagesUriArray state update here, then continue the rest of the function referencing the returned uploadedImages array from uploadImages.

const addItem = async () => {
  const uploadedImages = await uploadImages();
  setImageUriArray(imagesUriArray => [
    ...imagesUriArray, // <-- shallow copy
    ...uploadedImages, // <-- append new objects
  ]);

  const changes = ref(db, path);
  get(changes).then(async (snapshot) => {
    if (snapshot.val().data !== undefined) {
      const fetchedArray = snapshot.val().data;
      const object = {
        id: `${Math.random()}`,
        images: uploadedImages,
      };

      update(changes, { data: [...fetchedArray, object] });     
    }
  });
}
  • Related