In my camera component, I want to upload the photos to a storage bucket every 3 photos. I use state in react to save my image blobs to an array. Everything works perfectly for the first 3 photos but afterward, I am not able to reset my array to be empty again and a seemingly random number of photos is uploaded to my bucket.
let [blobs, setBlobs] = useState([]);
const capturePhoto = async () => {
const photo = await camera.current.takePhoto();
fetch(photo.path)
.then(res => {
setBlobs([...blobs, res._bodyBlob]);
console.log('blobs', blobs.length, blobs);
})
.catch(err => {
console.log('err', err);
});
checkLength();
};
const checkLength = async () => {
if (blobs.length >= 2) {
// upload files to a folder with the current date in a firebase cloud bucket
const datestring = new Date().toLocaleString('de-DE');
blobs.forEach((blob, i) => {
uploadFile(blob, datestring + '/' + (i + 1) + '.jpg');
});
// reset state
setBlobs([]);
sendNotification('Photos uploaded');
toggleDialog();
}
};
I console logged my array and the size only increases. Also, it starts console logging with zero although I already added an element likely because setState()
is asynchronous.
I tried to await the reset by wrapping it in a promise, but that sadly did not work either.
How can I upload the blobs to the cloud once there are 3 of them and reset the list afterward?
Looks like three things:
checkLength
is called before the fetch completes.setState
until the next render. This is a fundamental idea of React (debatable whether it's a good idea), state values are immutable during a render. setState
just gives the next immutable state that will be used by the next render.setState
depends on the previous state, you should pass a callback to setState
instead of using the current value directly. As an example, say you have an empty array, you call fetch once, then again before the first one completes. Both of these setState
calls would be referencing the empty array when doing ...blobs
. By passing a callback, setState
gets the most recent value passed in as a parameter. More info: https://react.dev/reference/react/Component#setstateEasiest solution is to pass the array as a parameter to checkLength
inside of the setState
callback.
Here's with .then()
as in the question:
const capturePhoto = async () => {
const photo = await camera.current.takePhoto();
fetch(photo.path)
.then(res => {
setBlobs(prev => {
const newBlobs = [...prev, res._bodyBlob];
console.log('blobs', newBlobs.length, newBlobs);
checkLength(newBlobs);
return newBlobs;
});
})
.catch(err => {
console.log('err', err);
});
};
and here's with async
await
const capturePhoto = async () => {
const photo = await camera.current.takePhoto();
const res = await fetch(photo.path).catch(console.error);
if (!res) return;
setBlobs(prev => {
const newBlobs = [...prev, res._bodyBlob];
console.log('blobs', newBlobs.length, newBlobs);
checkLength(newBlobs);
return newBlobs;
});
};
checkLength
const checkLength = async (newBlobs) => {
if (newBlobs.length >= 2) {
// upload files to a folder with the current date in a firebase cloud bucket
const datestring = new Date().toLocaleString('de-DE');
newBlobs.forEach((blob, i) => {
uploadFile(blob, datestring + '/' + (i + 1) + '.jpg');
});
// reset state
setBlobs([]);
sendNotification('Photos uploaded');
toggleDialog();
}
};