Why changes made in database not updates in realtime? - javascript

I am trying to learn firestore realtime functionality.
Here is my code where I fetch the data:
useEffect(() => {
let temp = [];
db.collection("users")
.doc(userId)
.onSnapshot((docs) => {
for (let t in docs.data().contacts) {
temp.push(docs.data().contacts[t]);
}
setContactArr(temp);
});
}, []);
Here is my database structure:
When I change the data in the database I am unable to see the change in realtime. I have to refresh the window to see the change.
Please guide me on what I am doing wrong.

Few issues with your useEffect hook:
You declared the temp array in the way that the array reference is persistent, setting data with setter function from useState requires the reference to be new in order to detect changes. So your temp array is updated (in a wrong way btw, you need to cleanup it due to now it will have duplicates) but React is not detectign changes due to the reference to array is not changed.
You are missing userId in the dependency array of useEffect. If userId is changed - you will continue getting the values for old userId.
onSnapshot returns the unsubscribe method, you have to call it on component unMount (or on deps array change) in order to stop this onSnapshot, or it will continue to work and it will be a leak.
useEffect(() => {
// no need to continue if userId is undefined or null
// (or '0' but i guess it is a string in your case)
if (!userId) return;
const unsub = db
.collection("users")
.doc(userId)
.onSnapshot((docs) => {
const newItems = Object.entries(
docs.data().contacts
).map(([key, values]) => ({ id: key, ...values }));
setContactArr(newItems);
});
// cleanup function
return () => {
unsub(); // unsubscribe
setContactArr([]); // clear contacts data (in case userId changed)
};
}, [userId]); // added userId

Related

Capturing data out of a firebase collection, and actually making it usable?

I have a collection in firebase called "polls". The collection is an array, and within the array I have 4 urls (url_a, url_b, url_c, and url_d). I want to pull these urls out of the array in my react app so I can use them as the src for 4 different images.
I know since "polls" is an array, I can't just pull the urls directly by setting some variable to "polls.data.url_a". With the 2nd useEffect function in the code below, I'm able to console.log the array
and the data within it using forEach. I can console.log poll.data, and even poll.data.url_a and it returns the proper result.
But I am stuck on how to get outside of that forEach and actually capture the individual urls to where I can assign them to a variable so I can make use of them.
import { useEffect, useState } from "react"
import { collection, getDocs } from "firebase/firestore"
import { db } from '../firebase.config'
function Food() {
const [polls, setPolls] = useState([])
useEffect(() => {
getPolls()
}, [])
** useEffect(() => {
polls.forEach((poll) =>
console.log(poll.data.url_a))
}, [polls])
**
function getPolls() {
const pollsRef = collection(db, 'polls');
getDocs(pollsRef).then(response => {
const poll = response.docs.map(doc => ({data: doc.data(), id: doc.id}))
setPolls(poll)
}).catch(error => console.log(error.message))
}
return (
<div>
Food
</div>
)
}
export default Food
I've tried setting a variable within the forEach and calling it later, and running a forEach later on in the function. But I just don't know how to get to the URL in a usable way, outside of just console logging it. Any help would be greatly appreciated, thanks!
Check out what Renaud suggested here to see how to assign variables: Get Firebase Collection into simple array
If I understand what you're asking, you'll need the poll const to instead point towards a global store variable or to reference it within the template section.
If the data your fetching is structured like this:
const polls = [{
data: {
url_a: "url a",
url_b: "url b"
}
}]
It's easier to just assign it to an object state variable instead of an array.
const [polls, setPolls] = useState({})
// ...
useEffect(() => {
getPolls()
}, [polls])
function getPolls() {
const pollsRef = collection(db, 'polls');
getDocs(pollsRef).then(response => {
const polls = response.docs.map(doc => ({data: doc.data(), id: doc.id}))
polls.map(poll => setPolls(poll.data))
}).catch(error => console.log(error.message))
}
// ...
return <img src={polls.url_a} alt="" />

Update only specific data in Realtime database of Firebase v9

I am new to firebase and using ReactJs, I am wondering how can I update a specific data in Realtime database, I want to change the value of it to true or false interchangeably.
This is my code.
const [status, setStatus] = useState(false);
const [tempId, setTempId] = useState("");
const [todo, setTodo] = useState("");
const updateStatus = () => {
update(ref(db, `/${auth.currentUser.uid}/${tempId}`), {
completed: setStatus(!status),
todo: todo,
id: tempId,
});
};
Thank you in Advance.
If you only want to update the status property, you can do:
update(ref(db, `/${auth.currentUser.uid}/${tempId}`), {
completed: !status
});
Since your calling update, it will leave the other properties of the node unmodified.
Alternatively, you can do:
update(ref(db, `/${auth.currentUser.uid}/${tempId}/status`), !status)
Or:
set(ref(db, `/${auth.currentUser.uid}/${tempId}/status`), !status)
Both of these write to the complete path of just the status property.
What you'll note is that I don't call setStatus in the code above. Instead I recommend listening to the value of the status node, and then calling setStatus when you get a callback there:
onValue(ref(db, `/${auth.currentUser.uid}/${tempId}/status`), (snapshot) => {
setStatus(snapshot.val());
});
Separating the read and write operations like this is known as command query responsibility segregation (or CQRS) and in the case of Firebase won't even be slower for the user, as the local SDK will immediately call the onValue handler when you call set or update on that path.

Document in nested collection is immediately removed after reappearing adding it to the collection

I'm using firestore and react to build my app.
This is how I add a document to the nested collection:
const handleWatch = () => {
if(watchedList.indexOf(foundProfile.id) > -1) {
alert('User already in list');
} else {
db.collection("profiles")
.doc(myProfile.id)
.collection("watched")
.add({
watchedUserId: foundProfile.id
})
.then(success => props.onWatchProfile())
}
}
And this is how I remove it:
const handleUnwatch = (id) => {
db.collection("profiles")
.doc(myProfile.id)
.collection("watched")
.where("watchedUserId", "==", id)
.onSnapshot(snapshot => snapshot.forEach(it => it.ref.delete()));
}
and I'm testing the following path:
Add a user with nick X
Delete user with nick X
Add a user with nick X
Delete user with nick X
Points 1 and 2 work perfectly.
During point 3 the user is added to the nested collection but immediately is deleted. In the firestore console, I see a blinking document.
What is the reason? Maybe it is problem because of firestore's cache?
I didn't test your code but I think this is because you don't detach the listener set in handleUnwatch. Therefore the listener is immediately triggered when you create a new doc and consequently deletes this doc (.onSnapshot(snapshot => snapshot.forEach(it => it.ref.delete()));).
The listener can be cancelled by calling the function that is returned when onSnapshot is called (doc). Depending on your exact workflow you need to call this function when necessary.
For example:
const handleUnwatch = (id) => {
const unsubscribe = db.collection("profiles")
.doc(myProfile.id)
.collection("watched")
.where("watchedUserId", "==", id)
.onSnapshot(snapshot => {
snapshot.forEach(it => it.ref.delete());
unsubscribe();
);
}
Again, the exact place where you call unsubscribe() depends on your exact functional requirements.

Firebase firestore : Unhandled Rejection (RangeError): Maximum call stack size exceeded

Started playing around with createAsyncThunk for learning purpose, decided to implement a shopping cart with firebase firestore but I ran into problems when trying to implement pagination in my react app.
How should I return the last visible state into my redux state during the initial load and subsequent load (infinite loading)
I am basing on code from redux tutorial sandbox :https://codesandbox.io/s/github/reduxjs/redux-essentials-example-app/tree/checkpoint-3-postRequests/?from-embed, but instead of connecting to a fake api, I am using firebase firestore.
Code to fetch product from firestore : ProductSlice.js
const InitialState = {
products : [],
status: 'idle',
error: null,
last: null, //to hold lastVisible when fetching data from firestore
}
export const fetchProducts = createAsyncThunk(types.RECEIVE_PRODUCTS, async (limit) => {
const resp = await fire_base_product.firestore()
.collection(collection_name).orderBy('id').limit(limit)
let result = resp.get().then((querySnapshot) => {
const lastVisible = querySnapshot.docs[querySnapshot.docs.length - 1] //how set this to redux state
const products = querySnapshot.docs.map((doc)=> {
return { ...doc.data()}
})
return {
products: products,
lastVisible: lastVisible
};
})
return result;
}
I am not quite sure on how to set this lastVisible data back into redux state, is it possible to do that with reference?
#Edit:
Tried to return both product list and last visible as an array and assign lastVisible in createSlice as stated below:
const productSlice = createSlice({
name:'products',
initialState:
reducers: {},
extraReducers:{
[fetchProducts.fulfilled]: (state, action) => {
state.products = state.products.concat(action.payload.products)
state.last = action.payload.lastVisible // this causes call stack error
}
}
});
With the above coding, two error will be reported if I run react app,
Trying to assign non serialize value into redux state
Maximum call stack size exceeded in firestore
I then tried to add middleware serializableCheck during create configuration as below:
export default configureStore({
middlleware: getDefaultMiddlleWare({
serializableCheck: {
//ignore action type
ignoredActions : ['RECEIVE_PRODUCTS/fulfilled']
// ignore path
ignoredPath: ['products.last']
}
}),
... // reducer codes
})
Even though now I have dismissed the first error, call stack exceeded still exists. Does anyone knows why this is happening ? Feel free to discuss if there is any workaround on this. Thanks.
Edit 2
Similar approach works when using context but does not work when using redux. Do I need to wrap return in promise as suggested in Firebase Unhandled error RangeError: Maximum call stack size exceeded ?
Did not managed to find a way to save lastVisible, but I found a workaround by just keeping track of the last retrieve id of my firestore data by saving it into redux state.
export const fetchProducts = createAsyncThunk(types.RECEIVE_PRODUCTS, async (limit) => {
const resp = await fire_base_product.firestore()
.collection(collection_name).orderBy('id').limit(limit)
let result = resp.get().then((querySnapshot) => {
var lastVisible = limit - 1; //only keep track of ID so we can avoid saving un-serialize coded
const products = querySnapshot.docs.map((doc)=> {
return { ...doc.data()}
})
return {
products: products,
lastVisible: lastVisible
};
})
return result;
}
And when during fetch of additional data we can then access the state by using getState() as below:
export const fetchMoreProducts = createAsyncThunk(types.LOAD_MORE_PRODUCTS, async (limit, {getState}) => {
const last = getState().products.last
var newProducts = await firebase_product.firestore()
.collection('store_products').orderBy('id')
.startAfter(last).limit(limit)
const result = newProducts.get().then((querySnapshot) => {
var lastVisible = last + limit;
const products = querySnapshot.docs.map((doc) => {
return { ...doc.data() }
})
return {
products : products,
lastVisible: lastVisible
}
})
// return retrieved data from firebase
return result
})
But doing this, I could skip the serialization check config all together as well. Not sure if this is the correct way, but this is how I got pagination working. Feel free to let me know if there is other way to approach this.
This error will come if you store non-serializable object in redux store!
if you are getting some non-serializable data from firebase, serialize it before storing it in redux store!
const nonSerializable = firestore().collection().doc(uid).get().data();
// if using this nonSerializable object in reducer, serialized it using JSON.parse(JSON.stringify())
const serializable = JSON.parse(JSON.stringify(nonSerializable))
you should save last visible path not the whole data like this
const lastVisible = querySnapshot.docs[querySnapshot.docs.length - 1].ref.path

Redux Action Creator Causing Infinite Loop

My application uses the eBay API and allows a user to save a list of favorite items.
I'm storing the user's Favorites data which includes the item ID for a product in a user field in my database.
I need to display the saved favorites yet with updated information from the eBay API, since the data saved (such as price, number of bids) is saved statically when a user selects a Favorite.
I've set up a separate action creator called getUpdates to the database which makes a call like this, and the call works fine.
ebayapi.com/itemID=id2342342,id3234234,id82829294,id234234234
In my Favorites component, i begin by making a call to the database to get the user's favorite items.
useEffect(() => {
props.getFavorites(props.loggedUser.id);
}, [props.loggedUser.id]);
This successfully returns and established my favorites state, which includes a list of the user's favorite items.
I'm trying to take each of the itemId's from the favorites state to pass to my ebay api to retrieve the updated data for each item.
Now, when I do something like what is shown below with a call to my getUpdates action creator...i'm getting an infinite loop, although it is storing the data properly in a new state field that I've defined favUpdates
const Favorites = props => {
useEffect(() => {
props.getFavorites(props.loggedUser.id);
}, [props.loggedUser.id]);
useEffect(() => {
setData(props.cardsToShow);
}, [props]);
const mapFAVS = props.favorites;
const data = Array.from(mapFAVS); //the favorites state is an array of objects so transforming data
const updatedFavs = data.map(item => item.id); //strips out the item id's
const formatFavs = updatedFavs.map(id => id.join(","));
props.getUpdates(formatFavs); //updates favUpdates state
I tried to change this so that the getUpdates call was within one of the useEffect hooks, like this...which also causes an infinite loop.
const Favorites = props => {
useEffect(() => {
props.getFavorites(props.loggedUser.id);
}, [props.loggedUser.id]);
useEffect(() => {
setData(props.cardsToShow);
}, [props]);
const mapFAVS = props.favorites;
const data = Array.from(mapFAVS);
const updatedFavs = data.map(item => item.id);
const formatFavs = updatedFavs.map(id => id.join(","));
useEffect(() => {
props.getUpdates(formatFavs);
}, [props]);
If i remove the second props argument the action creator does not get called and there is an axios error on the backend. Any ideas?
This was solved by adjusting the second argument in the useEffect hook.
useEffect(() => {
props.getUpdates(formatFavs);
}, [props.favorites]);

Categories

Resources