skip to Main Content

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)
enter image description here

But it does not send them up to firestore. Instead, this is what I get:
enter image description here

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


  1. I think you want to use arrayUnion()

    const newVals = {...values, imageList: arrayUnion(...imageArray)}
    

    FieldValue arrayUnion and Cloud FireStore with Flutter

    Login or Signup to reply.
  2. The trick here is that getDownloadURL() and uploadBytes() is an async function that happens to return a promise (as stated in the documentation). You can use await() to make the code execute more synchronously. See code below:

    const getLinks = async (values) => {
      const array = [];
      for await (const file of rawFiles) {
        const storageRef = ref(storage, `/houses/${file.name}`);
        const upload = await uploadBytes(storageRef, file);
        const imageUrl = await getDownloadURL(storageRef);
        array.push(imageUrl);
      }
      return array;
    };
    

    The above code will return an array instead of a promise.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search