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] });
}
});
}