Me and my friends are using Vue with firestore to create some functionality.
This is our first experience using either and we are having some trouble.
We are now trying to add a onSnapshot to a doc on firestore and assign said
data to a vue ref variable thus making it possible to use a watcher and respond to changes.
As can be seen in the image our doc contains 2 arrays which is the data we need to reach after a change has been observed by the onSnapshot. Our problem is that we dont know how to assign gameData to the appropriate data so that we can reach and use a watcher outside of the function.
Datastructure on firestore
export function realTimeGame(lobbyId) {
const gameCollection = firestore.collection('lobbies/' + lobbyId + '/game_data');
const gameData = ref([]);
const unsubscribe = gameCollection.doc('gameThings').onSnapshot(snapshot => {
// gameData.value = snapshot.map(document => ({ id: document.id, ...document.data() })) .map is not defined
gameData.value = snapshot; //this is essentially what we want to do
});
onUnmounted(unsubscribe)
return { gameData }
}
We found a solution:
export function realTimeGame(lobbyId) {
const gameData = ref();
const unsub = onSnapshot(doc(firestore,'lobbies/' + lobbyId + '/game_data/gameThings'),(doc) => {
gameData.value = { ...doc.data()}
})
onUnmounted(unsub)
return gameData
}
Related
New to coding last month and I'm learning JS through React Native.
I've added a Firestore DB to pull data through to my app and put together a call-back function using the useState hook due to it being Async.
My issue is my DBSearch function is now looping infinitely.
Code below:
const [propertyData, setPropertyData] = React.useState(["Well, this is awkward..."])
const colRef = (listName) => collection(db, listName)
const dbSearch = (listName, callBackFuncHere) => {onSnapshot(colRef(listName), (snapshot) => {
let newList = []
snapshot.docs.forEach(doc => {
newList.push({ ...doc.data(), id: doc.id }) // Pulls Object Data and runs for loop to assign to newList Array
})
callBackFuncHere(newList)
})};
function retFunc(newList){
setPropertyData(newList)
console.log('1')
}
dbSearch('propertyList', retFunc)
Is this an improper use for useState? and how should I change my dbSearch function to stop it continually looping?
Apologies if this has been asked a thousand times before, I'm unsure how to clearly articulate and search for my problem.
I want the useEffect to fetch and render rooms data. And rerender the data when the new room is being added to rooms
Code to fetch data and display data with useEffect and an empty dependency.
const [rooms, setRooms] = useState([]);
const getRoomsData = async () => {
const querySnapshot = await getDocs(collection(db, "rooms"));
const data = querySnapshot.docs.map((doc) => ({
id: doc.id,
data: doc.data(),
}));
setRooms(data);
};
useEffect(() => {
getRoomsData();
console.log("rerendered");
}, []);
With this code, after I added new room to rooms. I need to manually refresh the page to see the new data rendered. What should I do to make it rerender itself after new "room" is added to rooms?
Code to add room:
const roomName = prompt("please enter name for chat room");
if (roomName) {
addDoc(collection(db, "rooms"), {
name: roomName,
});
}
};
I tried adding rooms to the useEffect dependency, which has the result I want (no need to refresh), but it is only because it is rendering infinitely, which is bad. What is the proper way of doing it?
You are getting infinite re-renders because the function inside the useEffect is updating the state of rooms which is in the dependency array of the effect. This will always cause infinite re-renders.
To answer your question verbatim:
"How do I make it rerender only when the data is changed?"
Since rooms and data are set to be the same, you can keep your useEffect how it is, but then create another useEffect to fire only when the component mounts to call getRoomsData().
const [rooms, setRooms] = useState([]);
const getRoomsData = async () => {
const querySnapshot = await getDocs(collection(db, "rooms"));
const data = querySnapshot.docs.map((doc) => ({
id: doc.id,
data: doc.data(),
}));
setRooms(data);
};
useEffect(() = > {
getRoomsData();
}, [])
useEffect(() => {
console.log("rerendered");
}, [rooms]);
I think the real crux of solving your issue is knowing when to call getRoomsData(), because depending on that, you will change the useEffect dependency array to fit that need.
I can think of two approaches to solving this problem, without having to use useEffect, the first one being a workaround where you update the rooms state locally without having to fetch it from the server, i.e, you are basically constructing and appending an object to the existing array of rooms, this is possible only if you know the structure of the object, looking at your getRoomsData function, it seems that you are returning a data array with each item having the following object structure.
{
id: <DOCUMENT_ID>,
data: {
name: <ROOM_NAME>
}
}
So, when you add a room, you can do something like this:
const roomName = prompt("please enter name for chat room");
if (roomName) {
addDoc(collection(db, "rooms"), {
name: roomName,
});
let newRoom = {
id: Math.random(), //random id
data: {
name: roomName
}
}
setRooms([...rooms,newRoom])
};
This way, you can also reduce the number of network calls, which is a performance bonus, but the only downside is that you've to be sure about the object structure so that the results are painted accordingly.
In case, you are not sure of the object structure, what you could do is instead invoke getRoomsData after you add your new room to the collection, like so:
const roomName = prompt("please enter name for chat room");
if (roomName) {
addDoc(collection(db, "rooms"), {
name: roomName,
}).then(res=>{
getRoomsData();
})
};
This is would ensure to fetch the latest data (incl. the recently added room) and render your component with all the results.
In either of the methods you follow, you could use your useEffect only once when the component mounts with an empty dependency array.
useEffect(() => {
getRoomsData();
//console.log("rerendered");
}, []);
By doing this, you first basically display the data when the user visits this page for the first time/when the components mount for the first time. On subsequent additions, you can either append the newly added room as an object to the rooms array after adding it to the database or add it to the database and fetch all the results again, so that the latest addition is rendered on the screen.
wondering if anyone can assist me in this matter. I'm following the documentation for https://rnfirebase.io/firestore/usage. it does not work for my use case for some reason.
I just need to set the data, which it works and then read it back so i can push it onto my state and i'll render it.
I just can't read the data back properly. This addItemFunction is trigger when when user click on a button to add.
const addItemFunction = async (numb,exercise) =>{
firestore().collection(userEmail).get().then((snap) =>{
if(!snap.empty){
var finalID = uuid.v4();
firestore().collection(userEmail).doc(final).update({
[finalID]:{
exe:[exercise],
num:[numb],
}
}).then(() =>{
//RETURN SNAPSHOT NOT WORKING
console.log('user_added');
firestore().collection(userEmail).doc(final).onSnapshot(documentSnapshot =>{
console.log("here" + documentSnapshot.data());
});
}
Thanks for your time.
If you are using react with hooks I would suggest you put the onSnapshot listener in a useEffect hook:
useEffect(() => {
const unsubscribe = firestore
.collection(collectionName)
.doc(docId)
.onSnapshot(
(documentSnapshot) => {
const document = documentSnapshot.data();
console.log(document)
},
(error: Error) => {
throw error;
}
);
return () => unsubscribe();
}, [ docId, collectionName]);
this approach will separate concerns and the snapshots will run every time there is a change on the document, then where I put the console.log you could set the document to state.
Another approach will be to use get() instead of onSnapshot like:
const addItemFunction = async (numb,exercise) =>{
firestore().collection(userEmail).get().then((snap) =>{
if(!snap.empty){
var finalID = uuid.v4();
firestore().collection(userEmail).doc(final).update({
[finalID]:{
exe:[exercise],
num:[numb],
}
}).then(() =>{
console.log('user_added');
firestore().collection(userEmail).doc(final).get().then(() => {
console.log("here" + documentSnapshot.data());
})
}
}
}
this approach will not subscribe to changes and it will return the new updated document every time you call the addItemFunction
I created a react native app and have to refresh my screen every time in order to get the newly added data from firebase. I'm new to firebase and I thought I can use snapshot to get the current data but I still have to refresh my app every time a new event is created in order to see all the updated events on this view. Any help would be appreciated
export default function EventsHostedScreen() {
const navigation = useNavigation();
const [eventsData, setEventsData] = useState([]);
useEffect(() => {
async function fetchData() {
const currentUser = await firebase.auth().currentUser.uid;
result = [];
const eventsCollection = firebase.firestore().collection('events');
eventsCollection.get().then((snapshot) => {
snapshot.docs.forEach((doc) => {
if (doc.exists === true && doc.data().userId !== null) {
if (doc.data().userId === currentUser) {
result.push(doc.data());
}
}
});
setEventsData(result);
});
console.log('RESULT==>', result);
}
fetchData();
}, []);
You can listen to changes to a document or collection with the onSnapshot method. In addition to that, I would suggest a couple of changes to your code.
It seems to me like you want to query for documents where the userId is same as the current user's id. It would be easier to include this in the query with the where method. That way you won't have to filter the documents with if statements like you currently are. You will also save on Firestore reads, as right now you are getting all events, but with the where method you will only read the documents where the equality clause is true.
I would also include a check for whether you have the currentUser available, unless you are 100% sure this component won't ever be rendered while the currentUser is loading. And you don't need to await the currentUser and therefore don't need an async function anymore.
With these changes your useEffect could look something like the following.
useEffect(() => {
// Check if currentUser exists to avoid errors
if (!firebase.auth().currentUser) {
return;
}
const currentUser = firebase.auth().currentUser.uid;
// Create subscription to listen for changes
const unsubscribe = firebase
.firestore()
.collection('events')
.where('userId', '==', currentUser)
.onSnapshot((snapshot) => {
const result = [];
snapshot.forEach((doc) => {
result.push(doc.data());
});
setEventsData(result);
});
// Remove the listener when component unmounts
return () => unsubscribe();
// Add currentUser to useEffect dependency array, so useEffect runs when it changes
}, [firebase.auth().currentUser]);
I have a react component with this state
const [name, setName] = useState('')
const [comment, setComment] = useState('')
const [notes, setNotes] = useState([])
this function handles the input elements to fill the order
const handleComments = () => {
setNotes([...notes, {
name,
comment
}])
setName('')
setComment('')
}
and this function sends the info to the server
const update = async () => {
const newNotes = notes.map(note => ({
name,
comment
}))
return updateNotesPromise(newNotes)
}
here I have a button that has to execute both functions
<Button onClick={} />
How can I create a function that is passed through the onClick method and executes handleComments in order to load the info on the DOM and then, once that info there, executes the update function and saves the order info into the DB ?
It looks like you're using functional components, so you can create a useEffect that makes an API put request whenever notes gets updated:
useEffect(()=> {
updateNotesPromise(notes);
},[notes])
I'm assuming updateNotesPromise is a function that makes your request call? It's also unclear why newNotes is being mapped from notes, or why update is async when it doesn't await anything. Your onClick would simply trigger handleNotes (I'm assuming that is your submit button).
Here's a way to handle the component updating and server communicating with error handling:
const onButtonClicked = useCallback(async (name, comment) => {
// cache the olds notes
const oldNotes = [...notes];
// the updated notes
const newNotes = [...notes, {
name,
comment
}];
// update the component and assume the DB save is successful
setNotes(newNotes);
try {
// update the data to DB
await updateNotesPromise(newNotes);
} catch(ex) {
// when something went wrong, roll back the notes to the previous state
setNotes(oldNotes);
}
}, [notes]);