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

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();
}, [])

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?

useParam outside react component

I am trying to pass a variable value which uses useParam hook so i can pass it to my api which set outside of the component function.
VesselComponent.js :
import React, { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { fetchComponents } from "../../../features/componentsSlice";
import TreeItem from "#mui/lab/TreeItem";
import TreeView from "#mui/lab/TreeView";
import ExpandMoreIcon from "#mui/icons-material/ExpandMore";
import ChevronRightIcon from "#mui/icons-material/ChevronRight";
import { Link, Outlet, useParams } from "react-router-dom";
import axios from "axios";
export const api = async () => {
try {
const res = await axios.get(
// here
`http://127.0.0.1:8000/api/maintenance/${vesselId}`
);
return res.data;
} catch (error) {
console.log(error);
}
};
function VesselComponents() {
// this line
const vesselId = useParams();
const { components, error, loading } = useSelector(
(state) => state.components
);
// rest of the code
You can try to pass a param to api that would help you have vesselId from other places including useParams
export const api = async (vesselId) => {
try {
const res = await axios.get(
// here
`http://127.0.0.1:8000/api/maintenance/${vesselId}`
);
return res.data;
} catch (error) {
console.log(error);
}
};
Here is how we call it
const vesselId = useParams();
api(vesselId);
You can only use react hooks at the top level inside a component. You shouldn't call useParams in your api function. Instead, you should pass it to your api function and use some state to store the response from your API. Something like this:
export const api = async (vesselId) => {
try {
const res = await axios.get(
// here
`http://127.0.0.1:8000/api/maintenance/${vesselId}`
);
return res.data;
} catch (error) {
console.log(error);
}
};
function VesselComponents() {
// this line
const vesselId = useParams();
const [vesselData, setVesselData] = useState();
const { components, error, loading } = useSelector(
(state) => state.components
);
const fetchVesselData = async () => {
try {
const res = await api(vesselId);
setVessesData(res);
} catch (e) {
// handle error
}
}
useEffect(() => {
fetchVesselData()
});

Weird React Native Behavior

I have been building this mobile app with React Native/Expo and Firestore for a while now. The app schedules study sessions, and when a study session is active, a Pomodoro timer screen is to be shown, and when a session is inactive, the main homepage should be shown. However, I have been having trouble implementing this after a refactor to my database structure. Currently, for each schedule, a document is created in a subcollection corresponding to the user's UID. So, the path for a schedule would be "Users/(auth.currentUser.uid)/S-(auth.currentUser.uid)/(document id). To implement this feature, I have tried to run a function every second that checks through all of the documents and finds out whether a schedule is active, and if it is, it shows the Pomodoro timer screen. However, there is some weird behavior occurring. I am reading the database once using a Context Api, and the data shows perfectly in the screen where you view all your schedules, however in the function it is showing as an empty object. I have a feeling that it might be due to the bounds of the Context, however I am not sure. Does anyone know why?
CurrentDataProvider.js
import React, { createContext, useEffect, useState } from "react";
import {
doc,
getDocs,
onSnapshot,
collection,
query,
} from "firebase/firestore";
import { db, auth } from "../config/firebase";
export const CurrentDataContext = createContext({});
const CurrentDataProvider = ({ children }) => {
const [data, setData] = useState({});
useEffect(async () => {
if (auth.currentUser) {
const ref = query(
collection(
db,
"Users",
auth.currentUser.uid,
`S-${auth.currentUser.uid}`
)
);
const unsub = onSnapshot(ref, (querySnap) => {
let dat = {};
querySnap.forEach((doc) => {
dat[doc.id] = doc.data();
});
setData(dat);
});
return () => unsub;
}
}, []);
return (
<CurrentDataContext.Provider value={{ data, setData }}>
{children}
</CurrentDataContext.Provider>
);
};
export { CurrentDataProvider };
function being used to read schedules
const readSchedules = () => {
const currentTime = new Date();
Object.keys(data).forEach((key) => {
const clientSeconds =
currentTime.getHours() * 3600 + currentTime.getMinutes() * 60;
const startTimestamp = new Timestamp(data[key]["start"]["seconds"]);
const endTimestamp = new Timestamp(data[key]["end"].seconds);
const utcStartSeconds = startTimestamp.seconds;
const utcEndseconds = endTimestamp.seconds;
console.log(utcStartSeconds, clientSeconds, utcEndseconds);
const greaterTime = clientSeconds > utcStartSeconds;
const lessTime = clientSeconds < utcEndseconds;
const trueDay = data[key][dayOfWeekAsString(currentTime.getDay())];
if (trueDay) {
if (greaterTime && lessTime) {
setPomodoro(true);
setCurrentSchedule(key.toString());
console.log(`Schedule ${currentSchedule} selected!`);
return;
}
}
});
setPomodoro(false);
};
RootStack.js
import SplashScreen from "../screens/SplashScreen";
import AuthStack from "./AuthStack";
import React, { useState, useContext, useEffect } from "react";
import { View, ActivityIndicator } from "react-native";
import { auth } from "../config/firebase";
import { onAuthStateChanged } from "firebase/auth";
import { UserContext } from "./../components/UserProvider";
import { NavigationContainer } from "#react-navigation/native";
import FinalStack from "./MainStack";
import { CurrentDataProvider } from "../components/CurrentDataProvider";
const RootStack = () => {
const { user, setUser } = useContext(UserContext);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
const authListener = auth.onAuthStateChanged(async (user) => {
try {
await (user ? setUser(user) : setUser(null));
setTimeout(() => {
setIsLoading(false);
}, 3000);
} catch (err) {
console.log(err);
}
});
return authListener;
}, []);
if (isLoading) {
return <SplashScreen />;
}
return (
<NavigationContainer>
{user ? (
<CurrentDataProvider>
<FinalStack />
</CurrentDataProvider>
) : (
<AuthStack />
)}
</NavigationContainer>
);
};
export default RootStack;
Thanks for all your help!

useParams and useEffect, Parameter via ${id} in the useEffect function

This is the code, I receive the id in the console but not the response from the firestore server
import Item from './Item'
import { useState, useEffect } from "react";
import { useParams } from 'react-router-dom';
import {db} from "../Firebase"
import { collection, getDocs, query,} from "firebase/firestore";
const ContainerItems = () => {
let idParam = useParams();
const [ dataItem, setDataItem ] = useState([])
useEffect( () => {
async function fetchData(){
const querySnapshot = await getDocs(query(collection(db, `category/${(idParam)}/Items` )));
let dataArray = []
querySnapshot.forEach((doc) => {
dataArray.push({...doc.data(), id: doc.id});
});
setDataItem(dataArray)
console.log("Id",idParam)
}
fetchData();
}, [idParam])
return (
<>
{dataItem.map((data)=>(
<Item item={data} key={data.id}/>
))}
</>
)
}
export default ContainerItems;
the request to the server works, it just doesn't work when I add ${idParam}
You need to get the idParam out of the object using destructuring.
let { idParam } = useParams();
NOTE: Assuming you have named the path in the Route with the same name.
<Route path="/category/:idParam">...</Route>

How to store download urls and retrieve them from a collection

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]);

Resources