In my camera component I want to upload photos to a bucket every 3rd photo. I'm using state in React to save an image blob into an array. Everything works fine for the first 3 photos, but after that, I can't reset the array to empty and a seemingly random number of photos get 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 logged my array via console and the size only increased. Also, it starts console logging from scratch even though I've added an element, probably because setState() is asynchronous. I tried to wait for the reset by wrapping it in a promise, but unfortunately that didn't work either. Once there are 3 blobs, how do I upload them to the cloud and reset the list afterwards?
So it really depends on how the code is executed, specifically the asynchronous nature of setState so you can use the callback form of setState. Here is an example:
Here is the complete example with the rest of the code:
const capturePhoto = async () => { const photo = await camera.current.takePhoto(); fetch(photo.path) .then(res => { setBlobs(prevBlobs => [...prevBlobs, 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(); } };Looks like three things:
checkLengthbefore the fetch is complete.setStateuntil the next render. This is the basic idea of React (whether it's a good idea is debatable), state values are immutable during rendering.setStateJust gives the next immutable state that will be used by the next render.setStatedepends on a previous state, you should pass a callback tosetStaterather than using the current value directly. For example, let's say you have an empty array, you call fetch once, and then call fetch again before the first array is completed. BothsetStatecalls will reference empty arrays when executing...blob. By passing a callback,setStategets the latest value passed in as a parameter. More information:https://react.dev/reference/react/Component#setstateThe simplest solution is to pass the array as a parameter to
checkLengthinside thesetStatecallback.This is
.then()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); }); };This is
asyncawaitconst 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; }); };Check lengthconst 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(); } };