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.
Related
This is the structure of the json being fetched. I am trying to render some of the nested threads data to a web page with react.
import react, {useState, useEffect} from "react";
import axios from 'axios'
import ReactJson from 'react-json-view'
const FeaturedBoards = () => {
const [boards, setBoards] = useState([{page: '', threads: {}}]);
useEffect(() => {
fetchBoards();
}, []);
const fetchBoards = () => {
axios.get('https://a.4cdn.org/po/catalog.json')
.then((res) => {
console.log(res.data);
setBoards(res.data);
})
.catch((err) => {
console.log(err);
});
};
if(boards === 0) {
return <div>Loading...</div>;
}
else{
return (
<div>
<h1>Featured Boards</h1>
<div className='item-container'>
{boards.map((board) => (
<div className='board' key={board.id}>
<p>{board['threads']}</p>
</div>
))}
</div>
</div>
);
}
};
export default FeaturedBoards;
I have tried everything to display some of the nested threads data but nothing comes up. I've tried doing a second call to map on board but no luck, storing it in a variable and calling from that still nothing. Am I doing something totally wrong?
I believe this is more fully answered by How can I access and process nested objects, arrays or JSON?. but to explain for this particular data structure, keep reading.
Look at your actual data... boards is an array. Each element in it is an object with page (int) and threads (array) properties. Each threads array element is an object with other properties. You can use map to iterate arrays and return a JSX representation of the objects within.
For example
const [boards, setBoards] = useState([]); // start with an empty array
const [loading, setLoading] = useState(true)
useEffect(() => {
fetchBoards().then(() => setLoading(false))
}, []);
const fetchBoards = async () => {
const { data } = await axios.get('https://a.4cdn.org/po/catalog.json')
setBoards(data)
}
return loading ? <div>Loading...</div> : (
<div>
<h1>Featured Boards</h1>
<div className="item-container">
{boards.map(board => (
<div className="board" key={board.page}> <!-- 👈 note "page", not "id" -->
{board.threads.map(thread => (
<p>{thread.name}</p>
<p>{thread.sub}</p>
<p>{thread.com}</p>
<!-- etc -->
))}
</div>
))}
</div>
</div>
)
I've retrieved a list of categories using an API. Now I want to fetch images from an URL based on the categories. I tried using each category to fetch images from another API, but I'm not sure how to do it.
import React, { useEffect, useState } from 'react';
import './css/Category.css';
function Category() {
useEffect(() => {
fetchData();
getImage();
}, []);
const [categories, setCategories] = useState([]);
const [image, setImage] = useState('');
const fetchData = async () => {
const data = await fetch('https://opentdb.com/api_category.php')
const categories = await data.json();
console.log(categories.trivia_categories)
setCategories(categories.trivia_categories)
}
const getImage = async (name) => {
console.log(name)
const q = name.split(' ').join('+')
const img = await fetch(`https://pixabay.com/api/?key=apikey&q=${q}&image_type=photo`)
const image = await img.json();
console.log(image)
setImage(image.previewURL)
}
return (
<div className="categories">
Yesss
<div className="category-grid">
{categories.map(category => (
<div className="category">
{category.name}
<img src={getImage(category.name)} /> //do not know what to do here to fetch image of the respective category
</div>
))}
</div>
</div>
)
}
export default Category;
After changes suggested by Noah, I was able to show only one image.
const getImage = async (name) => {
const query = stringMan(name.name)
console.log(query)
const img = await fetch(`https://pixabay.com/api/?key=17160673-fd37d255ded620179ba954ce0&q=${query}&image_type=photo`)
const image = await img.json();
console.log(image)
setImage({ [name.name]: image.hits[0].largeImageURL })
}
return (
<div className="categories">
Yesss
<div className="category-grid">
{categories.map(category => (
<div className="category" key={category.id}>
{category.name}
<img key={category.id} src={image[category.name]} />
</div>
))}
</div>
</div>
)
There are a couple of changes that you can make here.
One issue that I see is that you have a single image variable, that's being re-used for every single category. So when you map over a list of categories (for example let's say we have categories: [history, science, and math]). The current code will call getImage three times, with history, science, and math as parameters.
However, there is only one state variable that is being written to. Which means the last execution of setImage is the only one that will be preserved.
So, you might want to change image from being the URL of a category image, to an object that has the shape:
{
history: [url],
science: [url],
math: [url]
}
The other change to make is that you are calling the getImage() function directly in the rendered output <img src={getImage(category.name)} />. Instead, this should simply use the value that was assigned to the image state: <img src={image} />.
To actually fetch the image, you can use the useEffect hook (https://reactjs.org/docs/hooks-effect.html) to react to changes to the categories variable. That might look something like:
useEffect(() => {
categories.forEach((c) => getImage(c));
}, [categories]);
The useEffect hook will invoke the function it is given, whenever the dependencies change. This will allow you to trigger the getImage function in response to changes to the categories.
There're lot of improvement that could be done as stated by #noah-callaway above/below but coming straight to the point you need to simply fix the URI creation logic to use encodeURIComponent like below:
import React, { useEffect, useState } from 'react';
function Category() {
useEffect(() => {
fetchData();
getImage();
}, []);
const [categories, setCategories] = useState([]);
const [image, setImage] = useState('');
const fetchData = async () => {
const data = await fetch('https://opentdb.com/api_category.php')
const categories = await data.json();
console.log(categories.trivia_categories)
setCategories(categories.trivia_categories)
}
const getImage = async (name) => {
return encodeURI(`https://pixabay.com/api/?key=apikey&q=${encodeURIComponent(name)}&image_type=photo`)
}
return (
<div className="categories">
Yesss
<div className="category-grid">
{categories.map(category => (
<div className="category">
{category.name}
<img src={getImage(category.name)} />
</div>
))}
</div>
</div>
)
}
don't have the api key so can't test but it'll give you something like
https://pixabay.com/api/?key=apikey&q=Entertainment%3A%20Comics&image_type=photo
good luck, hope it works.
I use the following component to add new data in a Google Cloud FireStore collection:
import React, { useState } from 'react';
import { db } from '../firebase';
const AddCard = ({ totalDoclNumbers }) => {
const [newOriginalText, setNewOriginalText] = useState([]);
const [newTranslatedText, setNewTranslatedText] = useState([]);
const nextNumber = totalDoclNumbers + 1;
const onAdd = async () => {
await db.collection('FlashCards').add({
originalText: newOriginalText,
translatedText: newTranslatedText,
customId: Number(nextNumber),
});
};
return (
<ul>
<li key={nextNumber}>
<input
type='text'
readOnly
defaultValue={nextNumber}
/>
<div>
<textarea
placeholder='English'
onChange={(e) => setNewOriginalText(e.target.value)}
/>
</div>
<textarea
placeholder='Translation'
onChange={(e) => setNewTranslatedText(e.target.value)}
/>
<button onClick={onAdd}>
Add
</button>
</li>
</ul>
);
};
export default AddCard;
The add button works and I can see that new data are added to the collection on Google server, however to be able to see those new data, I have to refresh the page.
In order to fix it, I decided to make use of onSnapshot function as follow:
import React, { useState } from 'react';
import { db } from '../firebase';
const AddCard = ({ totalDoclNumbers }) => {
const [newOriginalText, setNewOriginalText] = useState([]);
const [newTranslatedText, setNewTranslatedText] = useState([]);
const nextNumber = totalDoclNumbers + 1;
const onAdd = async () => {
await db.collection('FlashCards').add({
originalText: newOriginalText,
translatedText: newTranslatedText,
customId: Number(nextNumber),
});
};
const renderDocList = () => {
return (
<ul>
<li key={nextNumber}>
<input
type='text'
defaultValue={nextNumber}
/>
<div>
<textarea
placeholder='English'
onChange={(e) => setNewOriginalText(e.target.value)}
/>
</div>
<textarea
placeholder='Translation'
onChange={(e) => setNewTranslatedText(e.target.value)}
/>
<button onClick={onAdd}>
Add
</button>
</li>
</ul>
);
};
db.collection('FlashCards').onSnapshot((snapshot) => {
let changes = snapshot.docChanges();
changes.forEach((change) => {
if (change.type === 'added') {
renderDocList();
}
});
});
return <>{renderDocList()}</>;
};
export default AddCard;
But although the new data get added to FireStore collection but I cannot see any changes on the page. Still I have to refresh (reload) browser to see those changes.
What am I doing wrong?
I would have approached it in a different way - why upload and then load again?
When onAdd is called save your data to set state, the change in state will re-render your component with the new data.
If you insist on going the save\load path then try using the use effect hook combined with set state as mentioned here:
https://dev.to/chantastic/connect-useeffect-and-usestate-to-update-components-with-data-4aaj
Lots of the power of React.js is in state, use it :)
Nobody is going to see the result of the renderDocList call in this snippet:
db.collection('FlashCards').onSnapshot((snapshot) => {
let changes = snapshot.docChanges();
changes.forEach((change) => {
if (change.type === 'added') {
renderDocList();
}
});
});
If you want the UI to update, you need to tell React that there is new data for it to render. And that's what useState hooks are used for. It don't fully see how your code affects what is being rendered, but you'll need some call to a setter that modifies the state that your component renders.
So say that your original text comes from the database, you'd do:
db.collection('FlashCards').onSnapshot((snapshot) => {
let changes = snapshot.docChanges();
changes.forEach((change) => {
if (change.type === 'added') {
setNewOriginalText("new document added");
}
});
});
And that in turn will then rerender any components that display the original text.
Also see:
Can't fetch firebase collection with React, where I use setState instead of the useState hook.
NodeJs/Firestore/React- Storing Firestore query results into the state
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>
)
}
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)