I have a function useVenue that returns venue data from a call to firebase:
import { useState,useEffect } from 'react'
import { firebase } from './firebaseConfig'
export function useVenues (){
const [venues, setVenues] = useState([]);
useEffect(() => {
const venueArray = [];
const getAllVenues = async () => {
await firebase
.firestore()
.collection("venues")
.get()
.then((snapshot) => {
snapshot.forEach((venue) => {
venueArray.push(venue);
});
setVenues(venueArray);
});
};
getAllVenues();
}, []);
const [...venueData] = venues.map((venue) => {
const {
name,
photoUrl,
averageRating,
numRatings,
type,
address,
phone,
website,
reviews } = venue.data();
return ({
name: name,
photoUrl: photoUrl,
averageRating: averageRating,
numRatings: numRatings,
type: type,
id: venue.id,
reviews:reviews,
address:address,
phone:phone,
website:website
})
});
return {venueData}
};
This function is exported to venues.js where the venue data is destructured out and pass as props to MidSection.js:
venues.js
import { useParams } from 'react-router-dom';
import { useVenues } from '../useVenue';
import Header from '../components/Header'
import VenueDetails from '../components/venue-page/VenueDetails'
import MidSection from '../components/venue-page/MidSection';
import ReviewSection from '../components/venue-page/ReviewSection';
const Venue = () => {
let {id} = useParams()
const { venueData } = useVenues()
const filteredVenue = venueData.filter(item => {
return item.id === id
})
return(
<div>
<Header/>
<VenueDetails filteredVenue = {filteredVenue}/>
<MidSection filteredVenue = {filteredVenue}/>
<ReviewSection filteredVenue = {filteredVenue} id = {id}/>
</div>
)
}
export default Venue
Lastly, in mid section I want to pull some information out of the venue data, passed as props as filteredvenue. I'm extracting this data with the following function:
import { useEffect,useState } from 'react'
import { convertToStars } from "../../helperFunctions";
const MidSection = ({ filteredVenue }) => {
const extractRatings = () => {
const foodRatings = []
filteredVenue[0].reviews.map((rating) => {
foodRatings.push(rating.ratingFood)
})
return {foodRatings}
}
const {foodRatings} = extractRatings()
I logged out foodRatings and it returned the data I wanted. However when I refreshed the browser, the app crashed, giving the error:
Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'reviews')
I'm assuming this is some sort of asynchronous error and that the browser is rendering this component before the data has been returned. Unsure why this is happening since I'm using async/await in the initial firebase useVenues function, and the filteredVenue object is being mapped through elsewhere in this component with no problems. Suggestions?
import { firebase } from './firebaseConfig'
problem is with firebase config you are using, when it resolve but have error
Related
When I fetch data from firebase firestore using getStaticProps, it works perfectly but when I try implementing the logic of getting the details of each single item using getStaticPaths, I fail and get a 404 page. This is how my [id].js code looks like currently.
import React from 'react'
import { db } from '#/Firebase';
import {collection, getDoc} from "firebase/firestore";
const reference = collection(db, "abantu");
export const getStaticPaths= async () => {
const umuntu = await getDoc(reference);
const paths = umuntu.docs.map(doc => {
return {
params: { id: doc.id }
}
})
return {
paths,
fallback: false
}
}
export const getStaticProps = async (context) => {
const id = context.params.id;
const data = await getDoc(reference) + id;
return {
props: {
umuntu: data
}
}
}
function Details({umuntu}) {
return (
<div>
<h1>{umuntu.ibizo}</h1>
</div>
)
}
export default Details
I dont quite get where my logic is going wrong but where could I be going wrong?.
For finding the right page props for each of the paths that you generate from the database in the getStaticPaths function, you should be able to find each of the pages information based on the id field you are getting from each path, see it here:
export const getStaticProps = async (context) => {
const id = context.params.id;
const umuntu = await getDoc(reference);
const data = umuntu.docs.find((pageData) => pageData.id === id); // this will find the right page based on the id passed via page path
return {
props: {
data
},
};
};
function Details({ data }) {
return (
<div>
<h1>{data.ibizo}</h1>
</div>
);
}
export default Details;
I'm trying to add functionality to fetch the next 4 comments but when I click on the Load more comments button, it throws this error message.
Uncaught FirebaseError: Invalid query. You must not call startAt() or startAfter() before calling orderBy().
Anyone, please help me with this.
SolutionComments.js
import React, { useState } from "react"
import { useParams } from "react-router-dom"
import { useCollection } from "../../hooks/useCollection"
import Comment from "./Comment"
import CommentForm from "./CommentForm"
const SolutionComments = () => {
const [activeComment, setActiveComment] = useState(null)
const { id } = useParams()
const { documents, fetchMore } = useCollection(`solutions/${id}/comments`, null, 4, [
"createdAt",
"desc",
])
const fetchMoreComments = () => {
fetchMore(documents[documents.length - 1])
}
return (
<div className="mt-10">
<CommentForm docID={id} />
<div>
{documents &&
documents.map((comment) => (
<Comment
key={comment.id}
comment={comment}
replies={comment.replies}
activeComment={activeComment}
setActiveComment={setActiveComment}
/>
))}
</div>
<button onClick={fetchMoreComments} className="text-white bg-purple-500">
Load More Comments!
</button>
</div>
)
}
export default SolutionComments
useCollection hook:
import { useEffect, useRef, useState } from "react"
// firebase import
import {
collection,
limit,
onSnapshot,
orderBy,
query,
startAfter,
where,
} from "firebase/firestore"
import { db } from "../firebase/config"
export const useCollection = (c, _q, _l, _o) => {
const [documents, setDocuments] = useState([])
const [isLoading, setIsLoading] = useState(true)
const [error, setError] = useState(null)
// if we don't use a ref --> infinite loop in useEffect
// _query is an array and is "different" on every function call
const q = useRef(_q).current
const o = useRef(_o).current
let ref = collection(db, c)
useEffect(() => {
if (q) {
ref = query(ref, where(...q))
}
if (o) {
ref = query(ref, orderBy(...o))
}
if (_l) {
ref = query(ref, limit(_l))
}
const unsubscribe = onSnapshot(ref, (snapshot) => {
const results = []
snapshot.docs.forEach(
(doc) => {
results.push({ ...doc.data(), id: doc.id })
},
(error) => {
console.log(error)
setError("could not fetch the data")
}
)
// update state
setDocuments(results)
setIsLoading(false)
setError(null)
})
// unsubscribe on unmount
return unsubscribe
}, [c, q, _l, o, isLoading])
// I'm using this function to fetch next 4 comments including the previous ones.
const fetchMore = (doc) => {
ref = query(ref, orderBy(...o), startAfter(doc), limit(_l))
onSnapshot(ref, (snapshot) => {
const results = []
snapshot.docs.forEach(
(doc) => {
results.push({ ...doc.data(), id: doc.id })
},
(error) => {
console.log(error)
setError("could not fetch the data")
}
)
// update state
console.log(results)
setDocuments([...documents, results])
setIsLoading(false)
setError(null)
})
}
return { documents, fetchMore, error, isLoading }
}
You are using onSnapshot() to start your query. Since you are doing paging, it is likely you want to use getDocs() instead.
At the very least, you want to be sure to shut down your previous listener for onSnapshot() before starting a new one. But it is rarely all that useful/correct to use an onSnapshot() with paging. Things get hairy-complicated under that approach.
I have this useDocument Hook to fetch a single document from a Firebasev9 by passing id of the document.
Can I get the document by passing a slug instead of id?
useDocument Hook
import { useEffect, useState } from "react"
// firebase import
import { doc, onSnapshot } from "firebase/firestore"
import { db } from "../firebase/config"
export const useDocument = (c, id) => {
const [document, setDocument] = useState(null)
const [error, setError] = useState(null)
// realtime document data
useEffect(() => {
const ref = doc(db, c, id)
const unsubscribe = onSnapshot(
ref,
(snapshot) => {
// need to make sure the doc exists & has data
if (snapshot.data()) {
setDocument({ ...snapshot.data(), id: snapshot.id })
setError(null)
} else {
setError("No such document exists")
}
},
(err) => {
console.log(err.message)
setError("failed to get document")
}
)
// unsubscribe on unmount
return () => unsubscribe()
}, [c, id])
return { document, error }
}
you could do smth like
import { useEffect, useState } from "react"
// firebase import
import { doc, onSnapshot } from "firebase/firestore"
import { db } from "../firebase/config"
const useDocumentHook = (slug) => {
const [doc, setDoc] = useState(null)
useEffect(() => {
const docRef = db.collection("posts").doc(slug)
const unsubscribe = onSnapshot(docRef, (doc) => {
setDoc(doc)
})
return () => unsubscribe()
}, [slug])
return doc
}
If you have the slug as a field in the document data then you can use a Query to get that document as shown below:
// Query to fetch documents where slug field is equal to given SLUG
const q = query(collection(db, c), where("slug", "==", SLUG))
onSnapshot(q, snap => {
if (!snap.empty) {
const data = snap.docs[0].data()
// snap.docs would be an array with 1 document
// There could be multiple in case multiple posts have same slug by chance
console.log(data)
} else {
console.log("No documents found with given slug")
}
})
Firebase set function not working for nested dictionaries
const [appInitialized, setAppInitialized] = useState(false)
// Where FireBase is name of function that initializes firebase
FireBase().then(() => {
setAppInitialized(true)
})
const [user, loading, error] = useAuthState(firebase.auth());
useEffect(() => {
if (appInitialized) {
SingingInAnonymously()
}
}, [appInitialized, loading])
function SingingInAnonymously() {
if (!loading) {
if (!user && !loading) {
firebase.auth().signInAnonymously()
.then((response) => {
const reference = "Users/" + response.user.uid
console.log("RUNNING TO ADD USER", reference)
firebase.database().ref(reference).set(
{
CurrentAvatar: "https://firebasestorage.googleapis.com/v0/b/just-an-app-96ed6.appspot.com/o/Avatars%2FRad.png?alt=media&token=dae7e13b-ac3a-4fd9-a5e5-8342e84dc13e",
UnlockedAvatars: ["PushID1"]
}
).then((e) => console.log("success")).catch((e)=>console.log(e))
setAppInitialized(false)
}).catch((e) => {
})
}
}
}
I have also tried with
UnlockedAvatars: { "0" : "PushID1" }
if i change "set" to "push" it works, otherwise it doesn't work, doesn't give any error nor it goes to then statement.
also if i remove the nested dictionary UnlockedAvatars: ["PushID1"] / UnlockedAvatars: {"0": "PushID1"} it works
If i change reference to a constant string, it still works
I have tried it out and it actually works. I created a simple react app which was able to update the database successfully.
import React from 'react'
import ReactDOM from 'react-dom'
import firebase from "firebase/app";
import 'firebase/database'
const firebaseConfig = {
// ...
}
firebase.initializeApp(firebaseConfig);
var database = firebase.database()
const App = () => {
React.useEffect(() => {
let reference = 'cities'
database.ref(reference).set(
{
CurrentAvatar: "wer",
UnlockedAvatars: {"0": "PushID1"},
extraAvatars: ["PushID1"]
}
).then(() => console.log("success")).catch((e)=>console.log(e))
}, [])
return (
<div>App</div>
)
}
let container = document.getElementById('app');
ReactDOM.render(<App />, container)
[![Image showing database output][1]][1]
[1]: https://i.stack.imgur.com/Unyo2.png
I am currently working on a chat application and for some reason every time I pass in my array of messages as a prop to another component it passes in a number to the component instead of the message object. I have tried a lot of different methods of passing it in regarding using multiple components etc but it seems to still be passing in the number of elements for some reason. Any help is appreciated... code is below
Component receiving the props
import React, { useEffect } from 'react'
import Message from '../../Message/Message'
function Messages({ messages }) {
useEffect(() => {
console.log(messages)
}, [messages])
return (
<div>
test
</div>
)
}
export default Messages
// Import React dependencies.
import React, { useEffect, useState, } from "react";
// Import React dependencies.
import io from 'socket.io-client'
import axios from 'axios'
import Messages from './Messages/Messages'
import uuid from 'react-uuid'
import { Redirect } from 'react-router-dom'
// Import the Slate components and React plugin.
const ENDPOINT = 'http://localhost:5000/'
export const socket = io.connect(ENDPOINT)
const LiveChatFunction = ({ group_id }) => {
// Add the initial value when setting up our state.
const [message, setValue] = useState("")
const [user, setUser] = useState("")
const [groupId, setGroup] = useState('')
const [messages, setMessages] = useState([])
const [toLogin, userAuth] = useState(false)
useEffect(() => {
setGroup(group_id)
axios.post('http://localhost:5000/api/users/refresh_token', null, { withCredentials: true }).then(data => {
if (!data.data.accessToken) {
userAuth(true)
}
})
axios.get('http://localhost:5000/api/users/userInfo', { withCredentials: true }).then(data => {
setUser(data.data.user)
})
socket.on(`message-${group_id}`, data => {
setMessages(messages.push(data))
});
axios.get(`http://localhost:5000/live/${group_id}`).then(x => {
console.log(x.data)
})
}, [group_id, messages])
function setClick() {
const data = {
messageId: uuid(),
user,
groupId,
message
}
socket.emit('message', data)
}
if (toLogin) {
return (
<Redirect to="/login" />
)
}
return (
<div>
<input placeholder="message" type="text" onChange={value => {
setValue(value.target.value)
socket.emit('typing-message', { username: user, time: new Date() })
}} />
<button onClick={setClick}>Submit</button>
<Messages messages={messages} />
</div>
)
}
export default LiveChatFunction;
I have added some comments of what I think you can change:
useEffect(() => {
const recieveFunction = (data) => {
//using callback so no dependency on messages
setMessages((messages) => messages.push(data));
};
async function init() {
//next line is pointless, this runs when group_id
// has changed so something must have set it
// setGroup(group_id);
await axios //not sure if this should be done before listening to socket
.post(
'http://localhost:5000/api/users/refresh_token',
null,
{ withCredentials: true }
)
.then((data) => {
if (!data.data.accessToken) {
userAuth(true);
}
});
await axios
.get('http://localhost:5000/api/users/userInfo', {
withCredentials: true,
})
.then((data) => {
setUser(data.data.user);
});
//start listening to socket after user info is set
socket.on(`message-${group_id}`, recieveFunction);
axios
.get(`http://localhost:5000/live/${group_id}`)
.then((x) => {
console.log(x.data);
});
}
init();
//returning cleanup function, guessing socket.off exists
return () =>
socket.off(`message-${group_id}`, recieveFunction);
}, [group_id]); //no messages dependencies
console.log('messages are now:',messages);
If messages is still not set correctly then can you log it
So I think I found your problem:
In your useEffect hook, you're setting messages to the wrong thing.
socket.on(`message-${group_id}`, data => {
setMessages(messages.push(data))
});
An example:
const m = [].push();
console.log(m);
// m === 0
const n = [].push({});
console.log(n);
// n === 1
As you can see this is the index.
So what you need is:
socket.on(`message-${group_id}`, data => {
messages.push(data);
setMessages(messages);
});
This will set messages to the array of messages.