Get specific post from CloudFirestore with React JS - reactjs

So, I have a new problem. I have tried to solve this a couple of days without success.
I'm trying to learn react and has creating a reactjs-site where I showcasing videos.
First I had problem to get the ID from the query string, but sort of solved it.
The code in my video.js looks like this:
function getDataFromDB(props) {
const [times, setTimes] = useState([])
const db = firebase.firestore();
useEffect(() => {
fire.firestore().collection('videoEvent').onSnapshot((snapshot) => { //time is the db name
const newTimes = snapshot.docs.map((doc) => ({
id: doc.id,
...doc.data()
}))
setTimes(newTimes)
})
}, [])
return times
}
const Video = (props) => {
const videoEvent = getDataFromDB()
const vidId = props.match.params.id;
console.log(vidId)
const { project } = props;
return (
<div className='div1080'>
<div className='flex-container'>
{videoEvent.map((videoEvent) =>
<div key={videoEvent.id} className='item' ><img className='imagevideo' src={photo} alt='hej' />
<br /> {videoEvent.Author} {videoEvent.Price}
{props.match.params.id}
<Link to={'video/' + videoEvent.id}>{videoEvent.eventTitle}</Link>
</div>
)}
</div>
</div>
)
}
export default Video
So now it is looping all the posts, that works fine, but I just want to show a specific post, with the ID from 'props.match.params.id'.
Any suggestions?

To access a single document for which you know the ID, see the documentation on getting a single document and getting realtime updates. Based on that:
fire.firestore().collection('videoEvent').doc(props.match.params.id).onSnapshot((doc) => {
const newTimes = [{
id: doc.id,
...doc.data()
])
setTimes(newTimes)
})
You'll need to pass the ID to getDataFromDB, which you seem to have forgotten:
const videoEvent = getDataFromDB(props)

Related

React useState object not letting me access it's properties

I'm making a chat app and I'm trying to create dynamic chat pages based on room page names within firestore. My chat component updates the chatbox title depending on what it finds off of firestore. I have determined that the query I'm using does in fact pull the data correctly through console.log.
function Chat() {
const { roomId } = useParams();
const [roomDetails, setRoomDetails] = useState();
useEffect(() => {
const roomQuery = query(
collection(db, "rooms"),
where("name", "==", roomId)
);
if (roomId) {
const unsubscribe = onSnapshot(roomQuery, (snapshot) => {
setRoomDetails(snapshot.docs.map((doc) => ({ ...doc.data() })));
});
return () => {
unsubscribe();
};
}
}, [roomId]);
console.log(roomDetails);
return (
<div className="chat">
<div className="chat__header">
<div className="chat__headerLeft">
<h4 className="chat__channelName">
<strong>#{roomDetails?.name}</strong>
<StarBorderOutlinedIcon />
</h4>
</div>
<div className="chat__headerRight">
<p>
<InfoOutlinedIcon /> Details
</p>
</div>
</div>
</div>
);
}
What doesn't work is the bottom section
<h4 className="chat__channelName">
<strong>#{roomDetails?.name}</strong>
<StarBorderOutlinedIcon />
</h4>
Within my browser the chat title will be blank and I presume that it's because it isn't reading the data or the data isn't defined yet. Here is what the useState looks like in console.
console.log output
[{…}]0:
{name: 'general'}
length: 1
[[Prototype]]: Array(0)
Here's what Firestore looks like
I found the solution I changed the way the Data was updated into the useState
useEffect(() => {
const roomQuery = query(
collection(db, "rooms"),
where("name", "==", roomId)
);
if (roomId) {
const unsubscribe = onSnapshot(roomQuery, (snapshot) => {
setRoomDetails(snapshot.docs.at(0).data());
});
return () => {
unsubscribe();
};
}
}, [roomId]);
The way the data is rendered is the same. Hope this helps some future people.

How can I express variables with array in JSX?

I am a beginner learning ReactJs.
I am trying to express data from firebase.
when I put {quizes[0].quiz} this was working.
But, if I want to use 'qno' variable, what should I do?
import {useParams} from "react-router-dom";
import {dbService} from 'fbase';
const QuizPlay = () => {
const {cateId} = useParams();
const [qno, setQno] = useState(0);
const [quizes, setQuizes] = useState([]);
useEffect(() => {
dbService
.collection("quizes")
.where('cateId','==',cateId)
.orderBy("createdAt", "desc")
.onSnapshot((snapshot) => {
const quizArray = snapshot.docs.map((doc) => ({
id: doc.id,
...doc.data(),
}));
setQuizes(quizArray);
})
}, [cateId]);
return (
<div className="container">
{quizes[qno].quiz} <=== error
</div>
)
}
export default QuizPlay;
Before quizes has been populated quizes[qno] - (quizes[0]) - is undefined and therefore does not have a quiz property.
Try
<div className="container">
{quizes[qno]?.quiz}
</div>
assuming your compiler supports optional chaining, or if not:
<div className="container">
{quizes.length > 0 && quizes[qno].quiz}
</div>
I think it's happening because when the component is mounted the first time the quizzes haven't been loaded yet. So quizes is empty. Try this:
{quizes && quizes[qno].quiz}
Or
{quizes.length > 0 && quizes[qno].quiz}
Could you tell us exactly what error your getting?

React, unexpected multiple result when using map and fetch

I am building Weather App, my idea is to save city name in localStorage, pass a prop to child component, then iterate using map and display each in seperate child of the first child
The problem is that displayed data doubles/triples on render(depending on component when render occurs) so when I have for example city London and add city Berlin it will render:
London,London,Berlin
The problem is not in AddCity component, it's working correctly but in this mix of asynchronous setState/fetching and maping
Please see the code below
App(parent component)
const App = () => {
const [cities, setCities] = useState([]);
const addCity = (newCity)=>{
console.log('adding')
setCities([...cities, newCity]);
let cityId = localStorage.length;
localStorage.setItem(`city${cityId}`, newCity);
}
useEffect(() => {
loadCityFromLocalStore()
}, [])
const loadCityFromLocalStore =()=>{
setCities([...cities, ...Object.values(localStorage)])
}
return (
<div>
<Header />
<AddCity addCity={addCity}/>
<DisplayWeather displayWeather={cities}/>
</div>
)
}
DisplayWeather (first child)
const DisplayWeather = ({displayWeather}) => {
const apiKey = '4c97ef52cb86a6fa1cff027ac4a37671';
const [fetchData, setFetchData] = useState([]);
useEffect(() => {
displayWeather.map(async city=>{
const res =await fetch(`http://api.openweathermap.org/data/2.5/weather?q=${city}&units=metric&appid=${apiKey}`)
const data = await res.json();
setFetchData((fetchData=>[...fetchData , data]));
})
}, [displayWeather])
return (
<>
{fetchData.map(data=>(
<ul>
<Weather
data={data}/>
</ul>
))}
</>
)
}
Weather component
const Weather = ({data}) => {
return (
<li>
{data.name}
</li>
)
}
It looks like the problem comes from calling setFetchData for cities that you already added previously.
One easy way to fix it would be to store fetch data as an object instead of a dictionary so that you just override the data for the city in case it already exists (or maybe even skip the fetch as you already have the data).
For example:
const [fetchData, setFetchData] = useState({});
useEffect(() => {
displayWeather.map(async city=>{
const res = await fetch(`http://api.openweathermap.org/data/2.5/weather?q=${city}&units=metric&appid=${apiKey}`)
const data = await res.json();
setFetchData((fetchData=> ({...fetchData, [city]: data})));
})
}, [displayWeather])
Then, to map over fetch data you can just use Object.values:
return (
<>
{Object.values(fetchData).map(data=>(
<ul>
<Weather
data={data}/>
</ul>
))}
</>
)
If you want to skip already fetched cities you can do something like this instead:
useEffect(() => {
displayWeather.map(async city=>{
if (!fetchData[city]) {
const res = await fetch(`http://api.openweathermap.org/data/2.5/weather?q=${city}&units=metric&appid=${apiKey}`)
const data = await res.json();
setFetchData((fetchData=> ({...fetchData, [city]: data})));
}
})

I am having trouble to delete individual document from my firebase firestore database

I created a react app which lists book titles and author names. The data stores in firebase firestore database and it sync with the application at frontend. The problem I am having is that to delete individual document from the collection. I'm able to access the id but unable to single out particular id to delete. I put the code below and hoping somebody can helping me out to show how I should delete individual document, at the moment it deletes all the documents. Thanks!
import React, {useState, useEffect} from 'react'
import firebase from '../config/fbConfig'
const useBooks = () => {
const [books, setBooks] = useState([])
useEffect(() => {
firebase.firestore().collection('books').onSnapshot((snapshot) => {
const newBooks = snapshot.docs.map(doc => ({
id: doc.id,
...doc.data()
}))
setBooks(newBooks)
})
},[])
return books
}
const handleOnClick = () => {
const db = firebase.firestore()
db.collection('books').get().then(snapshot => {
snapshot.docs.forEach(doc => {
const id = doc.id
db.collection('books').doc(id).delete()
console.log("Jampa thinlay Book ID:", id)
})
})
}
const BookList = () => {
const books = useBooks()
console.log('jt books', books)
return(
<div className="book_list">
<ul>
{
books.map(book =>
<li key={book.id} onClick={()=> handleOnClick()} >
<div className="title">id:{book.id}</div>
<div className="cross" > X </div>
<div className="title">book:{book.title.title}</div>
<div className="author">author: {book.author.author}</div>
</li>
)
}
</ul>
</div>
)
}
export default BookList
the problem is that in your function
const handleOnClick = () => {
const db = firebase.firestore()
db.collection('books').get().then(snapshot => {
snapshot.docs.forEach(doc => {
const id = doc.id
db.collection('books').doc(id).delete()
console.log("Jampa thinlay Book ID:", id)
})
})
}
you are looping all the documents, and are calling delete on each of them, which then your issue happens.
a solution to this would be to change the signature of your handleOnClick function to recieve the id of the item to be deleted, like so
const handleOnClick = (documentId) => {
const db = firebase.firestore();
db.collection("books").doc(documentId).delete()
.then(() => {
console.log("item with id" + documentId + "got deleted");
}).catch((e) => console.log(e));
}
the difference of the new function is that it receive the id of the document you want to delete, and deletes only that document,
and in your html you can pass the id of each book as follows
const BookList = () => {
const books = useBooks()
console.log('jt books', books)
return(
<div className="book_list">
<ul>
{
books.map(book =>
<li key={book.id} onClick={()=> handleOnClick(book.id)} > // <-----------------
<div className="title">id:{book.id}</div>
<div className="cross" > X </div>
<div className="title">book:{book.title.title}</div>
<div className="author">author: {book.author.author}</div>
</li>
)
}
</ul>
</div>
)
}

React - How to find out the last document in a FireStore collection

The following component connects to a Cloud FireStore collection when it's mounted and shows only one document .limit(1).
Each time the user clicks the Next button a new request is sent to FireStore and shows the next document form the collection.
It's working fine. There's only one issue:
When the user clicks the Next button several times and reaches the last document inside the FireStore collection, the next click breaks the code, which makes sense.
I want that when the last document is reached, the next click on the Next button shows the first document or at least a message that the last item is reached.
How can I do this? How can I find out the last document in a FireStore collection?
import React, { useState, useEffect } from 'react';
import { db } from '../firebase';
const FlipCard = () => {
const [cards, setCards] = useState([]);
useEffect(() => {
const fetchData = async () => {
const data = await db
.collection('FlashCards')
.orderBy('customId', 'asc')
.limit(1)
.get();
setCards(data.docs.map((doc) => ({ ...doc.data(), id: doc.id })));
};
fetchData();
}, []);
const showNext = ({ card }) => {
const fetchNextData = async () => {
const data = await db
.collection('FlashCards')
.orderBy('customId', 'asc')
.limit(1)
.startAfter(card.customId)
.get();
setCards(data.docs.map((doc) => ({ ...doc.data(), id: doc.id })));
};
fetchNextData();
};
return (
<>
<div>
{cards.map((card) => (
<div className='card__face card__face--front'>
{<img src={card.imgURL} alt='' />}
{card.originalText}
</div>
<div className='card__face card__face--back'>
{card.translatedText}
</div>
</div>
))}
</div>
<div>
<button
onClick={() => showNext({ card: cards[cards.length - 1] })}
>
Next Card
</button>
</div>
</>
);
};
export default FlipCard;
The way to know that you're out of docs in a collection is when the number of results (cards.length) is smaller than the limit on the query (.limit(1)).
The code is breaking because it doesn't anticipate that condition:
onClick={() => showNext({ card: cards[cards.length - 1] })}
^ this expression might be -1
One way to fix is to conditionally render the button only if cards.length > 0.
Another way is to conditionally compute the parameter to showNext...
{ card: cards.length > 0 ? cards[cards.length - 1] : {} }
...and handle that case in the method.
just handle an error
const showNext = ({ card }) => {
const fetchNextData = async () => {
const data = await db
.collection('FlashCards')
.orderBy('customId', 'asc')
.limit(1)
.startAfter(card.customId)
.get()
.catch(error => { console.log('end of the row') });
if(!data.docs.length) console.log('end of the row');
setCards(data.docs.map((doc) => ({ ...doc.data(), id: doc.id })));
};
fetchNextData();
};

Resources