Upload multiple images to firebase then save image url to firestore - reactjs

Can someone assist me with revising the code so it will wait for the upload to finish then save the image urls to firestore? I'm new to async and await and can't seem to figure it out. The fileDownloadUrl is still empty even if I do the saving to firestore inside the Promise.all(promises).then():
cxonst promises = [];
const fileDownloadUrl = [];
pictures.forEach(file => {
const uploadTask =
firebase
.storage()
.ref()
.child(`img/upl/${file.data.name}`)
.put(file.uploadTask);
promises.push(uploadTask);
uploadTask.on(
firebase.storage.TaskEvent.STATE_CHANGED,
snapshot => {
const progress = Math.round((snapshot.bytesTransferred /
snapshot.totalBytes) * 100);
if (snapshot.state === firebase.storage.TaskState.RUNNING) {
console.log(`Progress: ${progress}%`);
}
},
error => console.log(error.code),
async () => {
const downloadURL = await
uploadTask.snapshot.ref.getDownloadURL();
fileDownloadUrl.push(downloadURL);
}
);
});
Promise.all(promises)
.then(() => {
db
.collection("properties")
.add({
timestamp: firebase.firestore.FieldValue.serverTimestamp(),
title: title,
description: description,
pictures: fileDownloadUrl,
user: user.uid
})
})
.catch(err => console.log(err));

While you are waiting for the promises of the put calls to complete, you're then using the uploadTask.on() to determine the download URL. Since this on is not part of the promise, there is no guarantee they're in sync.
A simpler and working approach should be:
const promises = pictures.map(file => {
const ref = firebase.storage().ref().child(`img/upl/${file.data.name}`);
return ref
.put(file.uploadTask)
.then(() => ref.getDownloadURL())
});
Promise.all(promises)
.then((fileDownloadUrls) => {
db
.collection("properties")
.add({
timestamp: firebase.firestore.FieldValue.serverTimestamp(),
title: title,
description: description,
pictures: fileDownloadUrls,
user: user.uid
})
})
.catch(err => console.log(err));

Related

Reactjs Async functions - function not finishing before next one starts

I am trying to build a recipe database. I created a function for firebase that allows you to upload a photo to the storage and then get the url for that photo.
const handleUpload = async () => {
const storage = firebase.storage();
const uploadTask = storage.ref(`images/${image.name}`).put(image);
uploadTask.on(
firebase.storage.TaskEvent.STATE_CHANGED,
(snapshot) => {},
(error) => {
console.log(error);
},
() => {
storage
.ref("images")
.child(image.name)
.getDownloadURL()
.then((url) => {
setImageUrl(url);
console.log(url);
});
}
);
};
Then I have a form where on the onSubmit, the photo upload function is supposed to fire so it can set the URL in the state, then fire the full upload which uploads the whole recipe to firebase with the url included. It is being handled inconsistently where sometimes it works and sometimes it doesn't. I have been trying different ways to make it work, but nothing seems to make it work perfectly. Here is the current onSubmit function:
onSubmit={async (values) => {
await handleUpload().then(() => {
createRecipe(values, imageUrl)
})
}}
Any help is much appreciated!
React updates state async. You code relies on the fact that the state update setImageUrl(url) will be handled before createRecipe is called.
But there is no guarantee for that. That's basically a race between reacts internal dealings with state update, and your chain of async function.
Better to make handleUpload return a promise to the URL.
const handleUpload = async () => {
return new Promise((resolve, reject) => {
const storage = firebase.storage();
const uploadTask = storage.ref(`images/${image.name}`).put(image);
uploadTask.on(
firebase.storage.TaskEvent.STATE_CHANGED,
(snapshot) => {},
(error) => {
reject(error);
},
() => {
storage
.ref("images")
.child(image.name)
.getDownloadURL()
.then((url) => {
resolve(url);
});
}
);
}
};
This way handleUpload will actually return the url when you await it.
onSubmit={async (values) => {
await handleUpload().then((url) => {
createRecipe(values, url)
})
}}
Also note that mixing async/await and then/catch/finally is considered bad practice.
I strongly recommend going with async/await only when you can.
onSubmit={async (values) => {
try {
const url = await handleUpload()
createRecipe(values, url)
} catch (err) {
console.log(err)
}
}}

How do I upload multiple images to firebase storage and at the same time store them as an array of downloadurls in firestore?

Im trying to upload images to firebase storage and then at the same time getdownloadurls and store them to firestore as an array of urls. Right now it stores an empty array of images on the first submit but when I submit again it stores the previous submitted images as urls in a new document.
const handleUpload = () => {
const promises = images.map((image) => {
const uploadTask = storage.ref(`images/${image.id}`).put(image);
uploadTask.on(
"state_changed",
(snapshot) => {
const progress = Math.round(
(snapshot.bytesTransferred / snapshot.totalBytes) * 100
);
setProgress(progress);
},
(error) => {
console.log(error);
},
async () => {
await storage
.ref("images")
.child(image.id)
.getDownloadURL()
.then((url) => {
setUrls((previousImage) => [...previousImage, url])
});
}
);
return uploadTask;
});
Promise.all(promises).then(() => {
db.collection('posts').doc(images.id).set({
confirmedPost,
name: user.name,
imageUrls: urls,
title: title,
description: description,
price: price,
phone: phone,
email: email,
timestamp: firebase.firestore.FieldValue.serverTimestamp()
}).catch(e => {console.log(e)});
})
.then(() => alert("All images uploaded"))
.catch((err) => console.log(err));
};

FirebaseError: Function updateDoc() called with invalid data

I'm writing a function uploadPost() inside that function I add a doc into firebase firestore collection than I choose a image and update doc by getting download url from firebase but I getting error like
FirebaseError: Function updateDoc() called with invalid data. Unsupported field value: a custom Promise object (found in field image in document posts/mcux46HsSK4mxycOIuev)
My function is given below
const uploadPost = async () => {
if (loading) return;
setLoading(true);
const docRef = await addDoc(collection(db, 'posts'), {
username: session.user.username,
caption: captionRef.current.value,
profileImg: session.user.image,
timestamp: serverTimestamp(),
})
const imageRef = ref(storage, `posts/${docRef.id}/image`);
await uploadString(imageRef, selectedFile, 'data_url').then(async (snapshot) => {
const downloadUrl = getDownloadURL(imageRef);
await updateDoc(doc(db, 'posts', docRef.id), {
image: downloadUrl
});
});
setOpen(false);
setLoading(false);
setSelectedFile(null);
}
What is wrong with that please give some suggestions for this.
const uploadPost = async () => {
if (loading) return;
setLoading(true);
const docRef = await addDoc(collection(db,'posts'), {
username: data?.user?.username,
caption: captionRef.current.value,
profileImg: data?.user?.image,
timestamp: serverTimestamp(),
})
const storage = getStorage(app);
const imageRef = ref(storage, `posts/${docRef.id}/image`);
await uploadString(imageRef, selectedFile, "data_url").then(async () => {
const downloadURL = await getDownloadURL(imageRef);
console.log('this is it' , downloadURL);
await updateDoc(doc(db, 'posts', docRef.id) , {
image: downloadURL,
})
}).catch((err) => {console.log(err)})
setOpen(false);
setLoading(false);
setSelectedFile(null);
}

i am trying to store the photo in the database (firebase) and in the storage. the photo got store in the Storage, but not added in the firestore

I am trying to store the photo in the database (firebase) and in the storage simultaneously. The photo got store in the Storage, but not added in the firestore. Any Help Please ?
My code below shows what I did to perform the task. Oh it is in reactJs
function Preview() {
const cameraImage = useSelector(selectCameraImage);
const navigate = useNavigate();
const dispatch = useDispatch();
// if there is no Image taken then return to homepage (navigate('../'), {replace:
true})
useEffect(()=>{
if(!cameraImage){
navigate('../', {replace: true});
}
}, [navigate, cameraImage])
const closePreview = ()=>{
dispatch(resetCameraImage())
}
const sendPost = () =>{
const id = uuid();
const uploadTask = storage
.ref(`posts/ ${id}`)
.putString(cameraImage, "data_url");
uploadTask.on('state_changed', null, (error) =>{
//error function
console.log(error);
},
//COMPLETE function**this is the storage*## storage ##
()=>{storage
.ref('post')
.child(id)
.getDownloadURL()
.then((url) => {
db.collection('posts').add({
imageUrl: url,
username: 'yannick Simo',
read: false,
//profilePic
timestamp: firebase.firestore.FieldValue.serverTimestamp(),
});
navigate('./chats', {replace: true});
})
})
}
return (
<div className="preview" >
<div onClick={sendPost} className="preview__footer">
<h2>Send Now</h2>
<SendIcon fontSize="small" className="preview__sendIcon" />
</div>
</div>);
}
export default Preview;
You're calling getDownloadURL on a different ref than you're uploading the image to.
The upload goes to
const uploadTask = storage
.ref(`posts/ ${id}`)
.putString(cameraImage, "data_url");
And then you get the download URL like this:
storage
.ref('post')
.child(id)
.getDownloadURL()
Note posts (plural) vs post (singular) in these references.
To prevent such mistakes, I recommend creating a single variable to hold the reference, and then using that everywhere:
const sendPost = () => {
const id = uuid();
const uploadRef = storage.ref(`posts/ ${id}`); // 👈
const uploadTask = ref.putString(cameraImage, "data_url"); // 👈
uploadTask.on('state_changed', null, (error) => {
//error function
console.log(error);
},
() => {
ref.getDownloadURL() // 👈
.then((url) => {
db.collection('posts').add({
imageUrl: url,
username: 'yannick Simo',
read: false,
//profilePic
timestamp: firebase.firestore.FieldValue.serverTimestamp(),
});
navigate('./chats', {
replace: true
});
})
})
}
As a final improvement, since you're not doing anything in the other callbacks, you can also just the fact that uploadTask is a promise itself, and use then() on it, to shorten the code and make it a bit more idiomatic:
const sendPost = () => {
const id = uuid();
const uploadRef = storage.ref(`posts/ ${id}`);
const uploadTask = ref.putString(cameraImage, "data_url");
uploadTask.then(() => { // 👈
ref.getDownloadURL()
.then((url) => {
db.collection('posts').add({
imageUrl: url,
username: 'yannick Simo',
read: false,
//profilePic
timestamp: firebase.firestore.FieldValue.serverTimestamp(),
});
navigate('./chats', {
replace: true
});
})
}).catch((error) => console.error(error)); // 👈
}
If you then use async/await of modern JavaScript, you can turn this into the even simpler to read:
const sendPost = () => async { // 👈
const id = uuid();
const uploadRef = storage.ref(`posts/ ${id}`);
await ref.putString(cameraImage, "data_url"); // 👈
const url = await ref.getDownloadURL() 👈
db.collection('posts').add({
imageUrl: url,
username: 'yannick Simo',
read: false,
//profilePic
timestamp: firebase.firestore.FieldValue.serverTimestamp(),
});
navigate('./chats', {
replace: true
});
}

React native problem saving data to firestore

Here is my code.
import FirebaseKeys from "./config";
import firebase from 'firebase/app';
class Fire {
constructor() {
firebase.initializeApp(FirebaseKeys);
}
addPost = async ({text, localUri}) => {
const remoteUri = await this.uploadPhotoAsync(localUri)
return new Promise((res, rej) => {
this.firestore.collection("posts").add({
text,
uid: this.uid,
timestamp: this.timestamp,
image: remoteUri
})
.then(ref => {
res(ref)
})
.catch(error => {
rej(error)
});
});
};
uploadPhotoAsync = async uri => {
const path = `photos/${this.uid}/${Date.now()}.jpg`
return new Promise(async (res, rej) => {
const response = await fetch(uri)
const file = await response.blob()
let upload = firebase.storage().ref(path).put(file)
upload.on(
"state_changed",
snapshot => {},
err => {
rej(err)
},
async () => {
const url = await upload.snapshot.ref.getDownloadURL();
res(url);
}
);
});
};
What I have noticed is there is no problem uploading image on uploadImageAsync but when it comes to addPost method it does not create a collection named "post".
what should I do with this? thankyou in advance. btw I am not using expo on this.
First of all, the collection that your addPost method is trying to create is called "posts", not "post". When it comes to Firestore, always check that your security rules allow for this user to write to that path.
It would also be nice to see the usage of addPost, and see what its res and rej return.

Resources