I am using Formik to handle a form submission to create new real estate properties, which includes image upload. I have everything else working: I have a function that first uploads all of the images to storage and returns the image links (the getLinks function), and then we add those URLs to the formik values object to try to upload them all to my forestore. This is the whole function.
const getLinks = async (values) => {
const array = [];
for await (const file of rawFiles) {
const storageRef = ref(storage, `/houses/${file.name}`);
uploadBytes(storageRef, file).then((snapshot) => {
getDownloadURL(snapshot.ref).then((url) => array.push(url));
});
}
return array;
};
onSubmit: async (values) => {
getLinks(values)
.then((imageArray) => {
const newVals = { ...values, imageList: imageArray };
return newVals;
})
.then(async (newValues) => {
console.log(newValues);
const docRef = await addDoc(collection(db, "properties"), newValues);
})
.finally(() => {
setSnackAlert({
type: "success",
message: "You provided values!! Congrats!",
});
handleOpen();
})
.catch((err) => {
setSnackAlert({
type: "error",
message: "There was an error handling your request",
});
handleOpen();
});
The frustrating part of this, however, is that the console log directly before we submit the values to my firestore CORRECTLY logs the object with the new image URLs. Here is the console log (I purposely cut it off for sensativity, but this array does have two images)
But it does not send them up to firestore. Instead, this is what I get:
Any help is much appreciated!
Here is some replication code to attempt such a problem yourself. The form is not as lengthy, but should work the same. You will need your own firebase info to try and replicate it.
import React, {useState} from 'react';
import './App.css';
import {useFormik} from 'formik'
import { collection, addDoc } from "firebase/firestore";
import { ref, uploadBytes, getDownloadURL } from "firebase/storage";
// YOU WILL NEED TO IMPORT YOUR OWN FIREBASE INFORMATION HERE FOR REFERENCE.
function App() {
const [images, setImages] = useState([])
const selectMultipleFiles = (e) => {
const raw = [];
const newImages = [];
raw.push(e.target.files);
for (let i = 0; i < raw[0].length; i++) {
newImages.push(URL.createObjectURL(raw[0][i]));
}
setImages(newImages);
};
const imageDisplay = images.map((image) => {
return <img src={image} style={{height: "50px", aspectRatio: "16 / 9"}}/>
})
const formik = useFormik({
initialValues: {
address: "",
price: null,
},
onSubmit: async (values) => {
getLinks(values)
.then((imageArray) => {
const newVals = { ...values, imageList: imageArray };
return newVals;
})
.then(async (newValues) => {
console.log(newValues);
const docRef = await addDoc(collection(db, "properties"), newValues);
})
.finally(() => {
alert("You provided values!! Congrats!")
});
})
.catch((err) => {
alert("Sorry, there was an error.")
});
},
})
return (
<main>
<h1>StackOvervlow Replication</h1>
<form onSubmit={formik.handleSubmit}>
<input
type="text"
name="address"
value={formik.values.address}
onChange={formik.handleChange}
/>
<input
type="number"
name="price"
value={formik.values.price}
onChange={formik.handleChange}
/>
<label htmlFor="raised-button-file">
<input
accept="image/*"
type="file"
multiple
onChange={selectMultipleFiles}
/>
</label>
<button type="submit">Submit</button>
{imageDisplay}
</form>
</main>
);
}
export default App;
2
Answers
I think you want to use
arrayUnion()
FieldValue arrayUnion and Cloud FireStore with Flutter
The trick here is that
getDownloadURL()
anduploadBytes()
is an async function that happens to return a promise (as stated in the documentation). You can useawait()
to make the code execute more synchronously. See code below:The above code will return an array instead of a promise.