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

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>

Related

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!

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

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

Get single data from state in React

I'm trying to make a page to show the details of each video.
I fetched multiple video data from the back-end and stored them as global state.
This code works if I go to the page through the link inside the app. But If I reload or open the URL directory from the browser, It can not load the single video data.
How should I do to make this work?
Thanx
Single Video Page
import { useState, useEffect, useContext } from "react";
import { useParams } from "react-router-dom";
import { VideoContext } from "../context/videoContext";
const SingleVideo = () => {
let { slug } = useParams();
const [videos, setVideos] = useContext(VideoContext);
const [video, setVideo] = useState([]);
useEffect(() => {
const result = videos.find((videos) => {
return videos.uuid === slug;
});
setVideo((video) => result);
}, []);
return (
<>
<div>
<h1>{video.title}</h1>
<p>{video.content}</p>
<img src={video.thumbnail} alt="" />
</div>
</>
);
};
export default SingleVideo;
Context
import React, { useState, createContext, useEffect } from "react";
import Axios from "axios";
import { AxiosResponse } from "axios";
export const VideoContext = createContext();
export const VideoProvider = (props) => {
const [videos, setVideos] = useState([]);
const config = {
headers: { "Access-Control-Allow-Origin": "*" },
};
useEffect(() => {
//Fetch Vidoes
Axios.get(`http://localhost:5000/videos`, config)
.then((res: AxiosResponse) => {
setVideos(res.data);
})
.catch((err) => {
console.log(err);
});
}, []);
return (
<VideoContext.Provider value={[videos, setVideos]}>
{props.children}
</VideoContext.Provider>
);
};
I think the reason is because when you refresh the app, you fetch the video data on context and the useEffect on your single video page component runs before you receive those data.
To fix you can simply modify slightly your useEffect in your single video component to update whenever you receive those data:
useEffect(() => {
if (videos.length) {
const result = videos.find((videos) => {
return videos.uuid === slug;
});
setVideo((video) => result);
}
}, [videos]);

Resources