Upload multiple files and query Firestore DB - ReactJS - reactjs

I have been reading quite a lot posts like this one How to upload multiple files to Firebase? about this question before and I tried different options but can’t get my result.
I am using React Hook Form for an insanely huge form that now I have to add an input more (to upload files), and need help doing everything at once.
My code to update one file:
const onChangeFile = async (e: any) => {
const file = e.target.files[0];
const storageRef = app.storage().ref();
const fileRef = storageRef.child(file.name);
await fileRef.put(file);
setFileUrl(await fileRef.getDownloadURL());
};
const onSubmit = async (data: any) => {
try {
await db
.collection('listings')
.doc()
.set({ ...data, created: new Date(), uid: currentUser.uid, file: fileUrl });
reset();
} catch (error) {
}
};
So, as you can see, uploading only one file is quite straightforward, but a different story is when uploading many. Thing is, I would need more functions inside my onSubmit, which is itself an async funcion, so that limits the amount of things I can do inside of it.
Does anyone have an simple workaround?

If e.target.files has multiple files then you can push the upload operations to an array and then upload them using Promise.all():
const onChangeFile = async (e: any) => {
const storageRef = app.storage().ref();
const fileUploads = e.target.files.map(file => storageRef.child(file.name).put(file));
const fileURLReqs = (await Promise.all(fileUploads)).map(f => f.ref.getDownloadURL())
const fileUrls = await Promise.all(fileURLReqs)
// setFileUrl(await fileRef.getDownloadURL());
};
fileUrls will be an array of URLs of all the uploads images which onSubmit can be uploaded to Firestore if needed.
I'm not sure if you are adding a new document for each image but if you are, try this:
// in onSubmit
const listingsCol = db.collection("listings")
const files = [] // get that fileUrls array from your state
const updates = files.map(file => listingsCol.add({...data, file}))
await Promise.all(updates)
// this will add a new document for each new file uploaded

Thanks to #Dharmaraj, I have arrived to a simple working version of this popular question, I think he has given a very clear and straightforward solution for it and the credit is totally his.
Nonetheless, my implementation could have weaknesses even though its working (so far), so I would like to post it, you can use it or point out possible red flags.
const Form: React.FC = () => {
const [filesUrl, setFilesUrl] = useState<any[]>([]);
const { register, handleSubmit, reset } = useForm({ defaultValues });
const onChangeFile = async (e: any) => {
const storageRef = app.storage().ref();
const fileUploads = Array.from(e.target.files).map((file: any) => storageRef.child(file.name).put(file));
const fileURLReqs = (await Promise.all(fileUploads)).map((f: any) => f.ref.getDownloadURL());
const fileUrls = await Promise.all(fileURLReqs);
setFilesUrl(fileUrls);
};
const onSubmit = async (data: any) => {
try {
await db
.collection('collName')
.doc()
.set({ ...data, created: new Date(), uid: currentUser.uid, file: filesUrl });
reset();
} catch (error) {
console.log(error)
}
};

Related

Unable to update hook after image is uploaded to firebase

First of all, thanks for the help...I'm quite new to React and I have NO IDEA why won't my hook update.
So I want to upload an image to firebase, grab the url from firebase, update it with the hook and send it to my db.
const [image, setImage] = useState(null);
const [imageURL, setImageURL] = useState({ preview: null, data: null });
// show preview every time the input changes
useEffect(() => {
// ensure image is present on the page and NOT null
if (image) setImageURL({ preview: URL.createObjectURL(image), data: null });
}, [image]);
const handleSubmit = async e => {
e.preventDefault();
try {
if (image) {
// create reference point to where the image is uploaded to
const imageRef = ref(storage, `${Date.now()} -- ${image.name}`);
// upload image & retrieve public url to save in db
await uploadBytes(imageRef, image);
const imageUrl = await getDownloadURL(imageRef);
setImageURL(prev => ({ ...prev, data: imageUrl })); <-- can't update hook
}
...other code
} catch (err) {
... handle error
}
<form onSubmit={handleSubmit}>
<label>
<span>Upload an image</span>
<input
type='file'
accept='image/*'
onChange={e => setImage(e.target.files[0])}
/>
</label>
</form>
I am using React 18 and Firebase 9.
I'm not sure what I'm doing wrong...so any help is greatly appreciated!
I have tried using async/await and then/catch, but both no luck
I don't know if I'm doing the right thing, but instead of updating the state, I simply post the returned URL from firebase to the db
Updated code:
const [image, setImage] = useState({ file: null, previewUrl: null });
useEffect(() => {
if (image.file)
setImage(prev => ({
...prev,
previewUrl: URL.createObjectURL(image.file),
}));
}, [image.file]);
const handleSubmit = async e => {
e.preventDefault();
let uploadedImage = null;
try {
if (image.file) {
uploadedImage = await uploadImage(image.file);
}
const res = await axios.post('/api/drinks', {
image: uploadedImage,
});
console.log(res);
} catch (err) {
...handle error
}
}
Everything works fine now, but again, I'm not sure if this is the right way to do it, so any feedbacks/comments are appreciated!

Upload more images from react-native app to firebase

I'm trying to upload multiple images from react-native to firebase. The way I go about this is by using expo image picker multiple - by monstrodev ( see snack.io here )
I've managed to get it to work, and managed to be able to choose multiple photos from the 'improvised' camera roll. They load fine in the add screen, but I cannot find a solution on how to upload them properly.
export default function Add(props) {
const [name, setName] = useState("");
const [photos, setPhotos] = useState(null);
const uploadImage = async () => {
const uri = photos[0].uri; // -> uri is like this because 'media' is an array with objects inside that contain name, type, and uri each, and I only need uri of each oject.
const childPath = `data/${firebase.auth().currentUser.uid}/${Math.random().toString(36)}`;
console.log(childPath);
const response = await fetch(uri);
const blob = await response.blob();
const upload = firebase
.storage()
.ref()
.child(childPath)
.put(blob);
const uploadProgress = snapshot => {
console.log(`transferred: ${snapshot.bytesTransferred}`)
};
const uploadComplete = () => {
upload.snapshot.ref.getDownloadURL().then((snapshot) =>{
addPost(snapshot);
console.log(snapshot);
})
};
const uploadError = snapshot => {
console.log(snapshot);
};
upload.on("state_changed", uploadProgress, uploadError, uploadComplete );
};
const addPost = (downloadURL) => {
firebase.firestore()
.collection("allPosts")
.collection(firebase.auth().currentUser.uid)
.collection('userPosts')
.add({
downloadURL,
name,
}).then((function () {
props.navigation.popToTop()
}))
}
useEffect(() => {
const {params} = props.route;
if(params) {
const {photos} = params;
if(photos) setPhotos(photos)
delete params.photos
}
}, [{photos}]) // This useEffect updates when coming back from the ImageBrowserScreen (can be found in snack.io, it's exactly the same)
The main problem is, let's say, I choose 3 photos. If I console.log(photos) I get this:
Array [
Object {
"name": "name1.JPG",
"type": "image/jpg",
"uri": "file:///name1.jpg",
},
Object {
"name": "name2.JPG",
"type": "image/jpg",
"uri": "file:///name2.jpg",
},
Object {
"name": "name3.JPG",
"type": "image/jpg",
"uri": "file:///name3.jpg",
},
]
The only I could get it to work was this, give exact path to uri (photos[0].uri for example) otherwise get network error. But this only uploads the first object/photo. I also tried to map through the photos state and return all uri's into a single array and use that as const uri, but that obviously didn't work, for uri needs only one string. So I somehow need to run that function for each uri to be able to get a downloadURL and store each of them.
EDIT:
const uploadImage = async (photo) => {
const uri = photo.uri;
const childPath = `data/${
firebase.auth().currentUser.uid
}/${Math.random().toString(36)}`;
console.log(childPath);
const response = await fetch(uri);
const blob = await response.blob();
const snapshot = await firebase.storage().ref().child(childPath).put(blob);
const downloadURL = await snapshot.ref.getDownloadURL();
imgs.push(downloadURL)
};
const uploadPhotos = async () => {
await Promise.all(photos.map(p=>uploadImage(photo)).then(addPost())
};
Can you try it with a loop trough all photos and upload them separately. Idealy using a Promise.all to upload them in parallel:
const addPost = async (downloadURLs) => {
await firebase.firestore()
.collection("allPosts")
.collection(firebase.auth().currentUser.uid)
.collection('userPosts')
.add({
downloadURLs,
name,
})
props.navigation.popToTop()
}
const uploadImage = async (photo) => {
const uri = photo.uri;
const childPath = `data/${
firebase.auth().currentUser.uid
}/${Math.random().toString(36)}`;
console.log(childPath);
const response = await fetch(uri);
const blob = await response.blob();
const snapshot = await firebase.storage().ref().child(childPath).put(blob);
const downloadURL = await snapshot.ref.getDownloadURL();
return downloadURL
};
const uploadPhotos = async () => {
const downloadURLs=await Promise.all(photos.map(p=>uploadImage(photo))
await addPost(downloadURLs);
};

Posting an array as part of a Schema with React and Axios

I have the following Schema:
const SubmitDebtSchema = new Schema ({
balance: [{newBalance: Number, balanceDate: Date}],
});
What I want to do is make a post request from my React frontend using Axios, and save the 'newBalance' as a number pulled from state and balanceDate as today's date.
However, I can't figure out how to access the 'newBalance' and balanceDate.
If I try the following, it just posts an empty array to my database:
onSubmit = async (e) => {
e.preventDefault();
const dayCurrent = new Date().toLocaleString();
await axios.post("/api/submit/submitDebt", {
newBalance: this.state.balance,
balanceDate: dayCurrent,
})
this.props.history.push('/dashboard');
}
And similarly, if I try the following, it errors out:
onSubmit = async (e) => {
e.preventDefault();
const dayCurrent = new Date().toLocaleString();
await axios.post("/api/submit/submitDebt", {
balance.newBalance: this.state.balance,
balance.balanceDate: dayCurrent,
})
this.props.history.push('/dashboard');
}
So how do I access my Schema's newBalance and balanceDate, and append new entries rather than replace the original ones?
Any feedback would be appreciated!

Firebase storage getImageURL after uploading to after save URL in firestore(React)?

I just want to upload an image then get is URL to save to firestore because i want to save the url of that image to an object. I just want the await to wait for the upload to be finished and then to get the url.
Problem is when i try to get the url it says it doesnt exist but when i go to firebase is there.
const fileData = await fileUpload(imageHome, values.newHomeTeamName);
const url = await storage.ref(fileData).getDownloadURL();
console.log(url);
const fileUpload = async (image: File, newHomeTeamName: string) => {
const fileName = formatFileName(image.name, newHomeTeamName);
const uploadTask = storage.ref(fileName).put(image);
await uploadTask.on(
'state_changed',
snapsphot => {},
error => {
console.log(error);
}
);
return fileName;
};
Your fileUpload function looks a bit unusual to me. You're using await on the on() call, but that doesn't return a promise. What you should do instead is wait on the task itself. So something like:
const fileUpload = async (image: File, newHomeTeamName: string) => {
const fileName = formatFileName(image.name, newHomeTeamName);
const uploadTask = storage.ref(fileName).put(image);
await uploadTask;
return fileName;
}
Or a bit simpler:
const fileUpload = async (image: File, newHomeTeamName: string) => {
const fileName = formatFileName(image.name, newHomeTeamName);
await storage.ref(fileName).put(image);
return fileName;
}
If you want to handle the error, you can catch it in there too. But since all you do is log it, I'd recommend letting it escape and leave it to the runtime to log it.
This is what i did for my app and the url does get stored in the firestore
//imports
import { storage, db } from './firebase'
import firebase from 'firebase'
//states or hooks
const [caption, setCaption] = useState('')
const [image, setImage] = useState(null)
const [progress, setProgress] = useState(0)
const handleUpload=()=>{
const uploadTask = storage.ref(`images/${image.name}`).put(image);
uploadTask.on(
'state_changed',
(snapshot)=>{
const progress = Math.round(
(snapshot.bytesTransferred / snapshot.totalBytes) *100
);
setProgress(progress)
},
(error)=>{
console.log(error);
alert(error.message);
},
()=>{
storage
.ref('images')
.child(image.name)
.getDownloadURL()
.then(url =>{
db.collection('posts').add({
timestamp: firebase.firestore.FieldValue.serverTimestamp(),
caption: caption,
imageUrl: url,
username: uploadname
});
setImage(null);
setProgress(0);
setCaption("");
})
}
)
}
The handleUpload trigress off when the upload button is clicked

How to upload image save in Storage and then get Url in DB

I am uploading the image into Storage in Firebase and DB, but I have an issue that
TypeError: _config__WEBPACK_IMPORTED_MODULE_1__.default.firestore is not a function
This is my code in API for my data, it has just DB not have storage yet, how can I put both images into storage and get downloadUrl in DB.
const firebaseDb = firebaseApp.database();
const fireStore = firebaseApp.firestore();
/**
* Representation for creating new product and save to database
*/
export const doCreateProduct = (productData, image_url) => {
const productId = firebaseDb.ref().push().key;
return firebaseDb
.ref(`products/${productId}`)
.set(productData)
.then(() => {
return onceGetProducts();
})
.catch((error) => ({
error,
status: "failure",
}));
};
And this is my handleImageFile in form
const [imageFile, setImageFile] = useState();
console.log(imageFile);
const handleFileChange = (event) => {
console.log("file image", event);
const image = event.target.files[0]
setImageFile(imageFile => (image))
};
//onSave is a button save after input
const onSave = () => {
createProductRequest(values, imageFile);
};
Anybody here can help me with this problem, please? Thank you so much

Resources