How to store download urls and retrieve them from a collection - reactjs

I have a form that sends data and images to firebase (firestore). I created a collection that only stores the urls. What I need is a way to query the different images urls based on a document reference ID because in my hierarchy, the last collection creates documents with unique ID and I'm unable to query them in order to get the image url.
Form.js
import { useSelector } from "react-redux";
import { db, storage } from "../../firebase";
import {
addDoc,
collection,
doc,
updateDoc,
} from "#firebase/firestore";
import { getDownloadURL, ref, uploadBytes } from "#firebase/storage";
import { useSession } from "next-auth/react";
function Form() {
const { data: session } = useSession();
const Images = useSelector((state) => state.draggedImages.images);
const imageTarget = Images.length - 1;
const SendPost = async () => {
const docRef = await addDoc(collection(db, "posts"), {
id: session.user.uid,
AdDescription: description,
});
Images[imageTarget].map((Img) => {
const imageRef = ref(storage, `posts/${docRef.id}/${Img.name}`);
uploadBytes(imageRef, Img, "data_url").then(async () => {
const downloadURL = await getDownloadURL(imageRef);
await updateDoc(doc(db, "posts", docRef.id), {
image: downloadURL,
});
// ---------------HERE IS THE PROBLEM--------------
await addDoc(collection(db, "ImageUrl", docRef.id, "Urls"), {
image: downloadURL,
});
// --------------------------------------------------
});
});
};
}
export default Form;
upon uploading the images, I have to fetch them into a carousel.
Carousel.js
import {
collection,
doc,
onSnapshot,
orderBy,
query,
getDocs,
} from "#firebase/firestore";
import { useRouter } from "next/router";
import React, { useEffect, useRef, useState } from "react";
import { db } from "../../firebase";
function Carousel() {
const [FetchedImages, setFetchedImages] = useState([]);
const router = useRouter();
const { id } = router.query;
useEffect(
() =>
onSnapshot(doc(db, `ImageUrl/${id}`), (snapshot) => {
setFetchedImages(snapshot.data());
}),
[db]
);
console.log("fetched : ", FetchedImages); // returns undefined
}
export default Carousel;

The defined hierarchy in the Form.js is pretty fine. The problem was actually the way to retrieve the data from Carousel.js using useEffect.
Following this resource , Here's the updated and working solution I used.
Carousel.js
useEffect(() => {
const FetchedImagesFromFirestore = async () => {
const querySnapshot = await getDocs(
collection(db, `ImageUrl/${id}/Urls`)
);
querySnapshot.forEach((doc) => {
setFetchedImages((prevState) => [...prevState, doc.data()]);
});
};
FetchedImagesFromFirestore();
}, [db]);

Related

currentUser uid undefined with getAuth hook

I'm a freshman in college and currently beginning with react and firebase in my free time. There is one thing I don't know why it doesn't works in my project.
const currentUser = useAuth()
const { documents: books } = useCollection("books", ["uid", "==", currentUser.uid])
the problem is that when i console i get ["uid", "==", undefined]
This is my useAuth hook
import { useState, useEffect } from 'react'
import { onAuthStateChanged } from "firebase/auth";
import { auth } from '../firebase/config'; //this is getAuth()
export function useAuth() {
const [currentUser, setCurrentUser] = useState();
useEffect(() => {
const unsub = onAuthStateChanged(auth, (user) => setCurrentUser(user));
return unsub;
}, [])
return currentUser;
}
and this is my hook to collect data from firestore
import { useState, useEffect, useRef } from "react"
import { db } from "../firebase/config" //this is getFirestore()
//firebase imports
import { collection, onSnapshot, query, where} from "firebase/firestore"
export const useCollection = (col, _q) => {
const [error, setError] = useState(null)
const [documents, setDocuments] = useState(null)
//set up query
const q = useRef(_q).current
useEffect(() => {
setError(null)
let ref = collection(db, col)
if (q) {
ref = query(ref, where(...q))
}
const unsub = onSnapshot(ref, (snapshot) => {
let results = []
snapshot.docs.forEach(doc => {
results.push({ id: doc.id, ...doc.data() })
})
setDocuments(results)
}, (err) => {
console.log(err.message)
setError(err.message)
})
return () => unsub()
}, [col, q])
return { documents, error }
}
I thought about something with sync or async, but could not find it.
Would someone have a solution and explain it to me?

Uploading Images to Firebase through a React App

I created a photo gallery app using React. When I upload an Image in the App, it is storing two documents of the image in the firestore, containing exactly the same 'created At' and 'url', but under two difference IDs. I want it to store only 1 document. I am not able to understand why my code is running twice and uploading the image twice?
Uploading is done using 'useStorage' custom hook:
import { useState, useEffect } from 'react'
import { projectStorage, projectFirestore, timestamp } from '../firebase/config'
const useStorage = (file) => {
const [progress, setProgress] = useState(0)
const [error, setError] = useState(null)
const [url, setUrl] = useState(null)
useEffect(() => {
// references
const storageRef = projectStorage.ref(file.name)
const collectionRef = projectFirestore.collection('images')
// uploading the file to the reference
storageRef.put(file).on(
'state_changed',
(snap) => {
let percentage = (snap.bytesTransferred / snap.totalBytes) * 100
setProgress(percentage)
},
(err) => {
setError(err)
},
async () => {
const url = await storageRef.getDownloadURL()
const createdAt = timestamp()
await collectionRef.add({ url, createdAt })
setUrl(url)
}
)
}, [file])
return { progress, url, error }
}
export default useStorage
'useFirestore' custom hook:
import { useEffect, useState } from 'react'
import { projectFirestore } from '../firebase/config'
const useFirestore = (collection) => {
const [docs, setDocs] = useState([])
useEffect(() => {
// return a function to un-subscribe from the collection
const unsub = projectFirestore
.collection(collection)
.orderBy('createdAt', 'desc')
.onSnapshot((snap) => {
let documents = []
snap.forEach((doc) => {
documents.push({ ...doc.data(), id: doc.id })
})
setDocs(documents)
})
// clean-up function
return () => unsub()
}, [collection])
return { docs }
}
export default useFirestore
Remove Strict Mode in index.js file, that is causing upload of file 2 times.

How to write data firestore database with document as user.uid as the doc id Firebase Modular 9

I am trying to post data after user payment went through to the database but all I get is an empty database document with no error at all on the front-end. Below codes are firebase-firestore rules and my payment Component Code.
My Payment Component code
import { CardElement, useElements, useStripe } from "#stripe/react-stripe-js";
import React, { useEffect, useState } from "react";
import CurrencyFormat from "react-currency-format";
import { Link, useNavigate } from "react-router-dom";
import { instance } from "../../axios";
import { useStateValue } from "../context/StateProvider";
import { getBasketTotal } from "../reducers/reducer";
import CheckOutProduct from "./CheckOutProduct";
import CircularProgress from "#mui/material/CircularProgress";
import "./payment.css";
import { setDoc, doc} from "firebase/firestore";
import { database } from "../config/firebaseConfig";
import { Alert } from "#mui/material";
const Payment = () => {
const [{ basket, user }, dispatch] = useStateValue();
const stripe = useStripe();
const elements = useElements();
const history = useNavigate();
const [succeded, setSucceded] = useState(false);
const [processing, setProcessing] = useState("");
const [error, setError] = useState(null);
const [disable, setDisable] = useState(true);
const [clientSecret, setclientSecret] = useState("");
const handleCardsubmit = async (e) => {
e.preventDefault();
// submits users requests
setProcessing(true);
const payload = await stripe
.confirmCardPayment(clientSecret, {
payment_method: {
card: elements.getElement(CardElement),
},
})
.then(({ paymentIntent }) => {
// paymentIntent is payment confirmation
const dbRef = doc(database, "orders", user?.uid);
setDoc(dbRef, {
amount:paymentIntent.amount,
basket:basket,
createdAt:paymentIntent.created
})
setError(null);
setSucceded(true);
setProcessing(false);
dispatch({
type: "EMPTY_BASKET",
});
history("/orders");
}).catch((error) => {
});
return payload;
};
My Firebase firestore rules code
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /orders/{userId}/{document=**} {
allow read, write, create: if request.auth.uid == userId;
}
}
}

get document array inside collections in firestore (React.js)

I have the following code, I seek to obtain the following a single document depending on the id
import React, {useState, useEffect } from 'react';
import { useParams } from 'react-router-dom';
import {db} from "../Firebase"
import { collection, getDocs, query } from "firebase/firestore";
import ItemProduct from "../components/ItemProduct"
import styled from "styled-components"
const Container = styled.div`
`
function PageItemProduct() {
const [ itemProduct, setItemProduct ] = useState([])
let { id } = useParams();
let { idProduct } = useParams()
useEffect( () => {
async function fetchData(){
const querySnapshot = await getDocs(query(collection(db, `category/${ id }/product/${idProduct}` )));
let itemArray = []
querySnapshot.forEach((doc) => {
itemArray.push({...doc.data(), id: doc.id});
});
setItemProduct(itemArray)
console.log(itemArray)
}
fetchData();
}, [idProduct])
return (
<Container>
<ItemProduct item= {itemProduct}/>
</Container>
)
}
export default PageItemProduct;
I get the following error.
"Uncaught (in promise) FirebaseError: Invalid collection reference. Collection references must have an odd number of segments, but category/M680J7hNdnGw8JZLmbwK/product/1S8YtahL4xeGrO7ELO3j has 4."
solution method
useEffect( () => {
async function fetchData(){
const querySnapshot = await getDoc(query(doc(db, `category/${ id }/product/${idProduct}`)));
if (querySnapshot.exists()) {
console.log("Document data:", querySnapshot.data());
setItemProduct(querySnapshot.data());
} else {
// doc.data() will be undefined in this case
console.log("No such document!");
}
}
fetchData();
}, [])

firebase: Cannot assign to read only property 'displayName' of object '#<Object>'

I'm using #reduxjs/toolkit: ^1.7.1 , firebase: 9.6.1 and firebase-tools: 10.0.1 on my ReactJS project.
I was trying to create a function where users can update their names and avatar photos.
For which I used updateProfile() function. But whenever I execute the update function it threw an error Cannot assign to read only property 'displayName' of object '#<Object>'.
There is an interesting thing I have noticed that is if I only update the photoURL property still it gives Cannot assign to read only property 'displayName' of object '#<Object>'
Project_Link_Github
Code: useFirebase.js:
import { getAuth, signInWithPopup, GoogleAuthProvider, createUserWithEmailAndPassword, signInWithEmailAndPassword, signOut, onAuthStateChanged, updateProfile } from 'firebase/auth';
import { getDownloadURL, getStorage, ref, uploadBytes } from 'firebase/storage';
import { useEffect, useState } from 'react';
import { useDispatch } from 'react-redux';
import { useLocation, useNavigate } from 'react-router-dom';
import { setIsLoading } from '../features/isloadingSlice';
import { login, logout } from '../features/userSlice';
import initAuth from '../Firebase/initAuth';
initAuth();
export const useFirebase = () => {
const auth = getAuth();
const location = useLocation();
const navigate = useNavigate();
const dispatch = useDispatch();
const [updateCount, setUpdateCount] = useState(0);
const storage = getStorage();
const Redirect = () => {
console.log(location);
const destination = location?.state?.from?.pathname || '/';
navigate(destination);
}
const uploadAvatar = async (file) => {
const fileRef = ref(storage, 'avatar/' + auth?.currentUser?.uid + '.png');
dispatch(setIsLoading(true));
const snapshot = await uploadBytes(fileRef, file);
const photoURL = await getDownloadURL(fileRef);
updateProfile(auth.currentUser, { photoURL }).then(() => {
setUpdateCount(updateCount + 1);
}).catch(e => console.log(e.message))
dispatch(setIsLoading(false));
console.log(snapshot);
}
const userRegister = (name, photoURL, email, password) => {
dispatch(setIsLoading(true));
createUserWithEmailAndPassword(auth, email, password)
.then(result => {
updateProfile(auth.currentUser, {
displayName: name, photoURL
}).then(() => { })
dispatch(login({ displayName: name, email, photoURL }));
Redirect();
}).catch(error => alert(error.message))
.finally(() => dispatch(setIsLoading(false)))
}
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (result) => {
if (result) {
dispatch(login({ ...result }))
}
else {
dispatch(login({}))
}
dispatch(setIsLoading(false));
})
return () => unsubscribe;
}, [updateCount, auth])
return {
logIn,
logOut,
Redirect,
uploadAvatar,
userRegister,
}
}
I don't know what's wrong with this displayName property but my previous project works fine.
Can anybody please help me with this?
I have 2 different projects using authentication of firebase, both using updateProfile() of firebase/auth. One of them works fine, but the other, which use #reduxjs/toolkit: ^1.8.4 got the same problem as yours, so i believe that it has something to do with reduxjs/toolkit. However, the function actually does work, the data has updated on server side (firebase). So in my case, i just ignore it.

Resources