1

I am creating a Post based application where a user can post pictures and text. I want to save the images on Firebase Storage bucket and get the image link immediately and store the image and the photo description text to my database

I am using Node, Express and Mongo DB . Below is my Code

import ModalStyles from "./_createpost.module.scss";
import {
    ref,
    uploadBytes,
    getDownloadURL
} from "firebase/storage";
import { storage } from "../../../config/firebaseConfig";
import { faCamera, faPhotoFilm } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useContext, useEffect, useState } from "react";
import UserContext from "../../../context/userContext";
import { uuid } from "uuidv4";
import { v4 } from "uuid";
import axios from "axios";



interface IModalProps {
}

const Modal = ({ }: IModalProps) => {
    const user = useContext(UserContext)
    const [imageUpload, setImageUpload] = useState<FileList | null>({} as FileList);
    const [postDesc, setPostDesc] = useState<string>('');
    const [imageUrls, setImageUrls] = useState<Array<string>>([]);


    const convertToBlob = (file: File): Promise<Blob> => {
        return new Promise((resolve, reject) => {
            const reader = new FileReader();
            reader.onloadend = () => {
                const blob = new Blob([reader.result as ArrayBuffer], { type: file.type });
                resolve(blob);
            };
            reader.onerror = reject;
            reader.readAsArrayBuffer(file);
        });
    };

    const uploadImages = async () => {

        if (!imageUpload) return;

        let promises: string[] = [];

        for (let index = 0; index < imageUpload.length; index++) {
            /* 
            const { name, type } = imageUpload[index];
            const file = new File([name], name, { type: type });
            const blob = await convertToBlob(file); 
            */
            const blob = await convertToBlob(imageUpload[index]);

            const imageRef = ref(storage, `images/${imageUpload[index].name + v4()}`);

            uploadBytes(imageRef, blob).then((snapshot) => {
                getDownloadURL(snapshot.ref).then((urls) => {
                    console.log(urls);
                    
                    promises.push(urls)
                    setImageUrls(prev => [...prev, urls]);
                });
            }).catch(err => console.log('An Error Occured!', err))
        }

        return promises;
    };

    const uploadFile = async (e: React.FormEvent) => {
        e.preventDefault();

        const imgs = uploadImages();
        console.log(imgs);
    
        if (!imgs) return;
           try {
               const response = await axios.post(`http://localhost:5000/post/create`, { creator_id: "64af540d576b8737651135a4", post_description: postDesc, photos: imgs });
               if (response.data) console.log('image uploaded successfully');
     
           } catch (error) {
               console.log('An Error occured while uploading the post');
     
           } 

    }
    const onchange = (event: React.ChangeEvent<HTMLInputElement>) => {
        const files = event.target.files;
        setImageUpload(files);

    }


    return <div className={ModalStyles.modal__container}>
        <h4 className={ModalStyles.modal__title}>Create Post</h4>
        <div className={`divider ${ModalStyles.modal__divider}`}></div>

        <div>
            <div className={`${ModalStyles.post__post_header} mt-1`}>
                <img src={user.profilePixs} className={ModalStyles.post__user_pixs} alt={user.firstname} />
                <div className={ModalStyles.post__post_info}>
                    <p className={ModalStyles.post__user_name}>{user.firstname} {user.lastname}</p>
                    <p>Public</p>
                </div>
            </div>
            <form className={ModalStyles.modal__post_inputField} onSubmit={uploadFile}>
                <textarea name="post_desc" id="post_desc" className={ModalStyles.modal__description} value={postDesc} onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => setPostDesc(e.target.value)}></textarea>
                <div className={`divider ${ModalStyles.modal__divider}`}></div>
                <p className={ModalStyles.modal__addPhotoText}>
                    <span>  Add to your Post</span>

                    <FontAwesomeIcon icon={faPhotoFilm} size="2xl" className={ModalStyles.modal__addPhotoIcon} />
                    <input type="file" name="photo" id="photo" multiple onChange={onchange} accept="image/png, image/jpeg" className={ModalStyles.modal__addPhotoIcon} />

                </p>
                <input type="submit" value="Post" className={ModalStyles.modal__submitBtn} />
            </form>
        </div>
    </div>;
};

export default Modal;

It always return an empty ARRAY of the images from the Promise.

Then after sending and EMPTY ARRAY to my DB, the urls are return. I know its due to Async function but I cant figure how to make it fininsh the first request before the next one as I want the urls to be available before making the next move

Tried the above code but no way forward

Orisfina
  • 59
  • 9
  • 1
    Not an answer but I don't think there's any reason to use your `convertToBlob` function. `File` is already a `Blob` – Phil Jul 15 '23 at 02:34
  • I removed it and still the same thing. The issue is there is a post request to upload the post to the DB even before the images finishes uploading to FIREBASE and return the URL – Orisfina Jul 15 '23 at 02:41
  • Why are you mixing use of then/catch inside the async function `uploadImages`? You can use await inside async to simply everything and force function to execute in order. – Doug Stevenson Jul 15 '23 at 12:38
  • @DougStevenson I tried based on my knowledge but didn't work. Below is what I resolved to and it's working but can you help me refine it? – Orisfina Jul 15 '23 at 12:40

1 Answers1

1

This is what I later resolved to and its working but I feel mixing then and try/catch doesn't correlate.

Below is my code

const uploadPost = async (e: React.FormEvent) => {
    e.preventDefault();

    if (!imageUpload) return;
    let postURLs: string[] = [];

    //Check if the image selected is NOT LESS THAN 2 or GREATER THAN 6
    if (imageUpload.length >= 2 && imageUpload.length <= 6 && (postDesc !== "")) {
        try {
            //Loop through FileList and upload each image to the Firebase Bucket
            for (let index = 0; index < imageUpload.length; index++) {
                const blob = imageUpload[index];
                const imageRef = ref(storage, `images/${imageUpload[index].name + v4()}`);

                await uploadBytes(imageRef, blob).then(async snapshot => {
                    await getDownloadURL(snapshot.ref).then((urls) => {
                        postURLs.push(urls);//Push each image url uploaded 
                    })
                })
            }
            const payload = { creator_id: "64af540d576b8737651135a4", post_description: postDesc, photos: postURLs };

            const response = await axios.post(`http://localhost:5000/post/create`, payload);

            if (response.data) toast.success(response.data.message);
        } catch (error) {
            toast.error('An Error Occured while Uploading  your Post');
        }
    }
    toast.error('Photos must be Greater than 2 and less than 6')
}

Someone should help me refactor it so everything can be in a try/catch block

Orisfina
  • 59
  • 9
  • You don't need to mix use of then and await in the same code. Then should be fully replaced by await. Read: [this](https://stackoverflow.com/questions/65571173/how-to-convert-then-catch-to-async-await), [this](https://stackoverflow.com/questions/66422355/convert-then-to-async-await-javascript) and anything else you find when doing a web search for "convert javascript then/catch to async/await" – Doug Stevenson Jul 15 '23 at 12:48