firebase set not accepting nested dictionary - javascript

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

Related

React useEffect hook will cause an infinite loop if I add tasksMap to the depedency array

If I add tasksMap to the useEffect dependency array below an infinite loop will happen. Can someone point me in the right direction on how to fix this? In order for the user to get an updated view of the tasks that have been added or modified, I need the app to call getProjectTasks and assign the returned map to the tasksMap. I do know that anytime you update state the component rerenders. I just havne't figured out how to do this without creating an infinite loop. Any help is greatly appreciated. Thank you.
import { useContext, useState, useEffect } from "react";
import { useParams } from "react-router-dom";
import { UserContext } from "../../contexts/user.context";
import { ProjectsContext } from "../../contexts/projects.context";
import { createProjectTask, getProjectTasks } from "../../utils/firebase/firebase.utils";
import OutlinedCard from "../../components/cards/TaskCard.component";
import { TextField, Button, } from "#mui/material";
import "./project.styles.css";
import "./project.styles.css";
const Project = () => {
const params = useParams();
const projectId = params.id;
const { currentUser } = useContext(UserContext);
const { projectsMap } = useContext(ProjectsContext);
const [taskName, setTaskName] = useState("");
const [tasksMap, setTasksMap] = useState({});
const project = Object.keys(projectsMap)
.filter((id) => id.includes(projectId))
.reduce((obj, id) => {
return Object.assign(obj, {
[id]: projectsMap[id],
});
}, {});
useEffect(() => {
console.log("running")
const getTasksMap = async () => {
const taskMap = await getProjectTasks(currentUser, projectId);
taskMap ? setTasksMap(taskMap) : setTasksMap({});
};
getTasksMap();
}, [projectId])
const handleChange = (event) => {
const { value } = event.target;
setTaskName(value);
};
const handleSubmit = async (event) => {
event.preventDefault();
try {
await createProjectTask(currentUser, projectId, taskName);
setTaskName("");
} catch (error) {
console.log(error);
}
};
return (
<div className="project-container">
{project[projectId] ? <h2>{project[projectId].name}</h2> : ""}
<form onSubmit={handleSubmit} className="task-form">
<TextField label="Project Task" onChange={handleChange} value={taskName}></TextField>
<Button type="submit" variant="contained">
Add Task
</Button>
</form>
<div className="tasks-container">
{Object.keys(tasksMap).map((id) => {
const task = tasksMap[id];
return (
<OutlinedCard key={id} projectId={projectId} taskId={id} name={task.name}></OutlinedCard>
);
})}
</div>
</div>
);
};
export default Project;
This is where the taskMap object comes from. For clarification, I'm using Firebase.
export const getProjectTasks = async(userAuth, projectId) => {
if(!userAuth || !projectId) return;
const tasksCollectionRef = collection(db, "users", userAuth.uid, "projects", projectId, "tasks")
const q = query(tasksCollectionRef);
try {
const querySnapshot = await getDocs(q);
const taskMap = querySnapshot.docs.reduce((acc, docSnapshot) => {
const id = docSnapshot.id;
const { name } = docSnapshot.data();
acc[id] = {id, name};
return acc;
}, {});
return taskMap;
} catch (error) {
console.log("Error getting task docs.");
}
};
The useEffect appears to be setting tasksMap when executed. Because this state is an object its reference will change everytime, which will produce an infinite loop

Uncaught FirebaseError: Invalid query. You must not call startAt() or startAfter() before calling orderBy()

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.

How to fetch a single document from a firebase v9 in react app?

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")
}
})

Possible async problem with firebase get request

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

React hooks array passing in number when passed into props of component

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.

Categories

Resources