push object to array react native - arrays

I need to get specific users who have the serviceClientID field in the firestore.
those that do, I insert it into the array and put it in my state chats (setChat).
but the problem is that only one user is entering my state and I have two users with this field.
why is only 1 entering and not 2?
code below:
firebase.auth().onAuthStateChanged(async ({ uid }) => {
const servicesCollection = await firestore()
.collection('Providers')
.doc(uid)
.collection('ServiceHistory')
.get();
servicesCollection.docs.forEach(async item => {
if (item.exists && item.data().serviceClientID) {
const clientsCollection = await firestore()
.collection('Clients')
.doc(item.data().serviceClientID)
.get();
// if (item.data().serviceClientID === clientsCollection.id) {
const values = {
id: clientsCollection.id,
name: clientsCollection.data().name.last,
uid,
};
const arr = [];
arr.push(values);
// }
console.log('arrayay', arr);
setChats(arr);
}
});
});

Cause every loop you empty an array.
You have to get the {arr} out of the function. then you need to push the data inside.
const firebaseFunc = () => {
let arr = [];
firebase.auth().onAuthStateChanged(async ({ uid }) => {
const servicesCollection = await firestore()
.collection('Providers')
.doc(uid)
.collection('ServiceHistory')
.get();
servicesCollection.docs.forEach(async item => {
if (item.exists && item.data().serviceClientID) {
const clientsCollection = await firestore()
.collection('Clients')
.doc(item.data().serviceClientID)
.get();
arr.push({
id: clientsCollection.id,
name: clientsCollection.data().name.last,
uid,
});
});
});
setChats(arr);
console.log('arrayay', arr);
}

Related

Trouble updating Doc in Firestore with React

Im trying to edit a document in my firestore db. I get an error i cant figure out.
Uncaught (in promise) FirebaseError: Expected type 'va', but it was: a custom Fh object
Im passing in an Object to the updateDoc function using the spread operator.
const saveEvent = React.useCallback(() => {
console.log(isChecked)
const checked = [];
isChecked.map((item, index) => {
if (item === true) {
checked.push(index + 1)
}
})
console.log(checked)
const newEvent = {
id: tempEvent.id,
title: popupEventTitle,
description: popupEventDescription,
start: popupEventDate[0].toString(),
end: popupEventDate[1].toString(),
allDay: popupEventAllDay,
status: popupEventStatus,
color: selectedColor,
resource: checked
};
if (isEdit) {
// update the event in the list
const index = myEvents.findIndex(x => x.id === tempEvent.id);
const newEventList = [...myEvents];
newEventList.splice(index, 1, newEvent);
console.log(newEventList)
setMyEvents(newEventList);
// ISSUE IS IN THE UpdateEvent function
const UpdateEvent = async () => {
const userRef = collection(database, 'events');
const q = query(userRef, where('id', '==', `${tempEvent.id}`));
const querySnapshot = await getDocs(q);
querySnapshot.forEach((doc) => {
console.log(newEvent)
updateDoc(doc, {
...newEvent,
});
})
}
UpdateEvent()
} else {
// add the new event to the list
setMyEvents([...myEvents, newEvent]);
const getEvents = async () => {
try {
const docRef = await addDoc(collection(database, "events"), {
id: tempEvent.id,
title: popupEventTitle,
description: popupEventDescription,
start: new Date(popupEventDate[0]),
end: new Date(popupEventDate[1]),
allDay: popupEventAllDay,
status: popupEventStatus,
color: selectedColor,
resource: checked
});
console.log("Document written with ID: ", docRef.id);
} catch (e) {
console.error("Error adding document: ", e);
}
}
//console.log(newEvent)
getEvents()
}
setSelectedDate(popupEventDate[0]);
setOpen(false);
}, [isEdit, myEvents, popupEventAllDay, popupEventDate, popupEventDescription, popupEventStatus, popupEventTitle, tempEvent, selectedColor, isChecked]);
Im not sure whats wrong, and googling the issue gives me little to work with. I cant find anything about Expected type 'va', but it was: a custom Fh object anywhere. Not even in the documentation..
Any help greatly appreciated.
EDIT:
Ater logging doc.query i noticed a small Va on the top of the document. Also a small "$h" when logging doc Anyone know anything more about that?
Screenshots:
This occurs when you're updating a document with incorrect document reference. You should use ref property to get the document reference to properly update the document on your foreach loop. See snippet below:
const UpdateEvent = async () => {
const userRef = collection(database, 'events');
const q = query(userRef, where('id', '==', `${tempEvent.id}`));
const querySnapshot = await getDocs(q);
querySnapshot.forEach((doc) => {
console.log(newEvent)
// Here. You shouldn't use the doc object itself.
// You must use the `reference` property to get the document reference to update to.
updateDoc(doc.ref, {
...newEvent,
});
})
}
UpdateEvent()
For more information, you may checkout this documentation.
// ISSUE IS IN THE UpdateEvent function
const UpdateEvent = async () => {
const userRef = collection(database, 'events');
const q = query(userRef, where('id', '==',${tempEvent.id}));
if you are using 'where' claouse then you will always get one doc
const querySnapshot = await getDocs(q);
const doc = await getDocs(q); try this
querySnapshot.forEach((doc) => {
console.log(newEvent)
updateDoc(doc, {
...newEvent,
});
})
}
UpdateEvent()
Try this
// ISSUE IS IN THE UpdateEvent function
const UpdateEvent = async () => {
const userRef = collection(database, 'events');
const q = query(userRef, where('id', '==', `${tempEvent.id}`));
const doc = await getDocs(q);
//querySnapshot.forEach((doc) => {
console.log(newEvent)
updateDoc(doc, {
...newEvent,
//});
})
}
UpdateEvent()

Getting UNDEFINED values from Firestore onSnapshot + Promises for my React State

I'm trying to make a Tweets Application with React and Firebase and I have been suffering when trying to get info from more than 1 collection.
So this is the story:
I get the tweets using onSnapshot. All fine here
I need more info from 2 other collections: user_preferences and user_photo, so I use .get() within the onSnapshot
For managing asynchronism, I resolve my 2 promises before returning the tweet data + details data object for my map function.
I made a console.log of my mappedTweet and the values are OKEY. Here I can see the tweet data + details data
But my STATE "tweets" just have an array of undefined objects =(. It shows the right number of rows accoroding to my Tweets collection but rows of undefined data, and not the rows of my mappedTweets objects. Why?
Can anyone shed some light?
useEffect(() => {
//------------getting the TWEETS with onSnapshot()-------------
const cancelSuscription = firestore
.collection('tweets')
.onSnapshot((snapshot) => {
const promises = [];
const tweetsMapped = snapshot.docs.map((doc) => {
let tweetAndAuthor;
const tweetMappped = {
text: doc.data().text,
likes: doc.data().likes,
email: doc.data().email,
created: doc.data().created,
uid: doc.data().uid,
id: doc.id,
};
let authorPreference, authorPhoto;
const userPreferencePromise = firestore
.collection('user_preferences')
.where('uid', '==', tweetMappped.uid)
.get();
const userPhotoPromise = firestore
.collection('user_photos')
.where('id', '==', tweetMappped.uid)
.get();
promises.push(userPreferencePromise);
promises.push(userPhotoPromise);
//------------getting the AUTHOR USER PREFERENCES with .get()-------------
userPreferencePromise.then((snapshot2) => {
authorPreference = snapshot2.docs.map((doc) => {
return {
username: doc.data().username,
color: doc.data().color,
};
});
});
//------------getting the AUTHOR PHOTO with .get()-------------
userPhotoPromise.then((snapshot3) => {
authorPhoto = snapshot3.docs.map((doc) => {
return {
photoURL: doc.data().photoURL,
};
});
});
Promise.all(promises).then((x) => {
return {
...tweetMappped,
author: authorPreference[0].username,
authorColor: authorPreference[0].color,
authorPhoto: authorPhoto[0].photoURL,
};
});
});
Promise.all(promises).then((x) => {
setTweets(tweetsMapped);
});
});
return () => cancelSuscription();
}, []);
Well, I made it work by changing the model I was using to retrieve the data from Firebase.
I was using an outer onSnapshot with nested promises (I think I was very near here), but now I'm using nested onSnapshots and now app is behaving as expected.
So this is the new useEffect
useEffect(() => {
let cancelUserPrefSuscription, cancelUserPhotoSuscription;
// First onSnapshot
const cancelTweetSuscription = firestore
.collection('tweets')
.onSnapshot((tweetSnapshot) => {
const list = [];
tweetSnapshot.docs.forEach((tweetDoc) => {
//Second onSnapshot
cancelUserPrefSuscription = firestore
.collection('user_preferences')
.where('uid', '==', tweetDoc.data().uid)
.onSnapshot((userPrefSnapshot) => {
userPrefSnapshot.docs.forEach((userPrefDoc) => {
//Third onSnapshot
cancelUserPhotoSuscription = firestore
.collection('user_photos')
.where('id', '==', tweetDoc.data().uid)
.onSnapshot((userPhotoSnapshot) => {
userPhotoSnapshot.docs.forEach((userPhotoDoc) => {
//Taking the whole data i need from all snapshots
const newData = {
id: tweetDoc.id,
...tweetDoc.data(),
author: userPrefDoc.data().username,
authorColor: userPrefDoc.data().color,
authorPhoto: userPhotoDoc.data().photoURL,
};
list.push(newData);
//Updating my state
if (tweetSnapshot.docs.length === list.length) {
setTweets(list);
}
});
});
});
});
});
});
return () => {
cancelTweetSuscription();
cancelUserPrefSuscription();
cancelUserPhotoSuscription();
};
}, []);
Edit: Fix from comments of above code
Author: #samthecodingman
For each call to onSnapshot, you should keep track of its unsubscribe function and keep an array filled with the unsubscribe functions of any nested listeners. When an update is received, unsubscribe each nested listener, clear the array of nested unsubscribe functions and then insert each new nested listener into the array. For each onSnapshot listener attached, a single unsubscribe function should be created that cleans up the listener itself along with any nested listeners.
Note: Instead of using this approach, create a Tweet component that pulls the author's name and photo inside it.
useEffect(() => {
// helper function
const callIt = (unsub) => unsub();
// First onSnapshot
const tweetsNestedCancelListenerCallbacks = [];
const tweetsCancelListenerCallback = firestore
.collection('tweets')
.onSnapshot((tweetSnapshot) => {
const newTweets = [];
const expectedTweetCount = tweetSnapshot.docs.length;
// cancel nested subscriptions
tweetsNestedCancelListenerCallbacks.forEach(callIt);
// clear the array, but don't lose the reference
tweetsNestedCancelListenerCallbacks.length = 0;
tweetsNestedCancelListenerCallbacks.push(
...tweetSnapshot.docs
.map((tweetDoc) => { // (tweetDoc) => Unsubscribe
const tweetId = tweetDoc.id;
//Second onSnapshot
const userPrefNestedCancelListenerCallbacks = [];
const userPrefCancelListenerCallback = firestore
.collection('user_preferences')
.where('uid', '==', tweetDoc.data().uid)
.limitToFirst(1)
.onSnapshot((userPrefSnapshot) => {
const userPrefDoc = userPrefSnapshot.docs[0];
// cancel nested subscriptions
userPrefNestedCancelListenerCallbacks.forEach(callIt);
// clear the array, but don't lose the reference
userPrefNestedCancelListenerCallbacks.length = 0;
//Third onSnapshot
const userPhotoCancelListenerCallback = firestore
.collection('user_photos')
.where('id', '==', tweetDoc.data().uid)
.limitToFirst(1)
.onSnapshot((userPhotoSnapshot) => {
const userPhotoDoc = userPhotoSnapshot.docs[0];
// Taking the whole data I need from all snapshots
const newData = {
id: tweetId,
...tweetDoc.data(),
author: userPrefDoc.data().username,
authorColor: userPrefDoc.data().color,
authorPhoto: userPhotoDoc.data().photoURL,
};
const existingTweetObject = tweets.find(t => t.id === tweetId);
if (existingTweetObject) {
// merge in changes to existing tweet
Object.assign(existingTweetObject, newData);
if (expectedTweetCount === newTweets.length) {
setTweets([...newTweets]); // force rerender with new info
}
} else {
// fresh tweet
tweets.push(newData);
if (expectedTweetCount === newTweets.length) {
setTweets(newTweets); // trigger initial render
}
}
});
userPrefNestedCancelListenerCallbacks.push(userPhotoCancelListenerCallback);
});
// return an Unsubscribe callback for this listener and its nested listeners.
return () => {
userPrefCancelListenerCallback();
userPrefNestedCancelListenerCallbacks.forEach(callIt);
}
})
);
});
// return an Unsubscribe callback for this listener and its nested listeners.
return () => {
tweetsCancelListenerCallback();
tweetsNestedCancelListenerCallbacks.forEach(callIt);
};
}, []);
Edit: Splitting the code in two components
Note: Changed limitToFirst(1) --> limit(1). Splitting the fetch logic in two components simplified the onSnapshot approach!
1.The Parent Component
useEffect(() => {
const tweetsUnsubscribeCallback = firestore
.collection('tweets')
.onSnapshot((tweetSnapshot) => {
const mappedtweets = tweetSnapshot.docs.map((tweetDoc) => {
return {
id: tweetDoc.id,
...tweetDoc.data(),
};
});
setTweets(mappedtweets);
});
return () => tweetsUnsubscribeCallback();
}, []);
2.The Child Component: Tweet
useEffect(() => {
// Helper Function
const unSubscribe = (unsub) => unsub();
//------------getting the AUTHOR USER PREFERENCE
const userPrefNestedUnsubscribeCallbacks = [];
const userPrefUnsubscribeCallback = firestore
.collection('user_preferences')
.where('uid', '==', tweet.uid)
.limit(1)
.onSnapshot((userPrefSnapshot) => {
userPrefNestedUnsubscribeCallbacks.forEach(unSubscribe); // cancel nested subscriptions
userPrefNestedUnsubscribeCallbacks.length = 0; // clear the array, but don't lose the reference
//------------getting the AUTHOR PHOTO
const userPhotoUnsubscribeCallback = firestore
.collection('user_photos')
.where('id', '==', tweet.uid)
.limit(1)
.onSnapshot((userPhotoSnapshot) => {
// Taking the whole data I need from all snapshots
setAuthor({
author: userPrefSnapshot.docs[0].data().username,
authorColor: userPrefSnapshot.docs[0].data().color,
authorPhoto: userPhotoSnapshot.docs[0].data().photoURL,
});
});
userPrefNestedUnsubscribeCallbacks.push(userPhotoUnsubscribeCallback);
});
return () => {
userPrefUnsubscribeCallback();
userPrefNestedUnsubscribeCallbacks.forEach(unSubscribe);
};
}, []);
Basically, you've pushed the promises to your promise array in the state they were before you you processed their data. You want to make use of the Promise.all(docs.map((doc) => Promise<Result>)) pattern here where each document should return a single Promise containing its final result. This then means that the Promise.all will resolve with Result[].
Note: If inside a Promise you are mutating a variable outside of the Promise (e.g. pushing to an array), that is generally a sign that you are doing something wrong and you should rearrange your code.
Here's a quick example of throwing this together:
useEffect(() => {
let unsubscribed = false;
//------------getting the TWEETS with onSnapshot()-------------
const cancelSuscription = firestore
.collection('tweets')
.onSnapshot((snapshot) => {
const tweetsMappedPromises = snapshot.docs.map((doc) => {
let tweetAndAuthor;
const tweetMappped = {
text: doc.data().text,
likes: doc.data().likes,
email: doc.data().email,
created: doc.data().created,
uid: doc.data().uid,
id: doc.id,
};
//------------getting the AUTHOR USER PREFERENCES with .get()-------------
const userPreferencePromise = firestore
.collection('user_preferences')
.where('uid', '==', tweetMappped.uid)
.limitToFirst(1)
.get()
.then((prefDocQuerySnapshot) => {
const firstPrefDoc = photoDocQuerySnapshot.docs[0];
const { username, color } = firstPrefDoc.data();
return { username, color };
});
//------------getting the AUTHOR PHOTO with .get()-------------
const userPhotoPromise = firestore
.collection('user_photos')
.where('id', '==', tweetMappped.uid)
.limitToFirst(1)
.get()
.then((photoDocQuerySnapshot) => {
const firstPhotoDoc = photoDocQuerySnapshot.docs[0];
return firstPhotoDoc.get("photoURL");
});
//--------------------assemble this result---------------------
return Promises.all([userPreferencePromise, userPhotoPromise])
.then(([authorPreference, authorPhoto]) => {
return {
...tweetMappped,
author: authorPreference.username,
authorColor: authorPreference.color,
authorPhoto: authorPhoto.photoURL,
};
});
});
Promise.all(tweetsMappedPromises)
.then((tweetsMapped) => {
if (unsubscribed) return; // ignore result, dealing with out of date data
setTweets(tweetsMapped);
})
.catch((err) => {
if (unsubscribed) return; // ignore result, dealing with out of date data
// important! handle errors
});
});
return () => {
unsubscribed = true;
cancelSuscription();
}
}, []);
Notes:
You may benefit from using async/await syntax here instead.
On new onSnapshot calls, snapshot.docChanges() can be used to make it more efficient and speed up rerenders by only updating the entries that have changed (e.g. added/removed/modified). You would use setTweets(previousTweetsMapped => /* newTweetsMapped */) for this.

Reactjs - Firebase : Cancel Old Requests

I'm new to Firebase Realtime Database, and i'm trying to implement a search field that allow users to search for other users and view their profiles.
The Problem Is:
I want to make the search realTime(on each input change).but whenever a new request's sent, the old request is still working in the backend which's causing unexpected behavior,i've wrapped this functionality in a useEffect Hook,old sideEffects has to be cleaned up to make the query results predictable,how can i abort the previous request.
useSearchOwner Custom Hook:
const useSearchOwner = () => {
const [{ SearchValue, SearchResult, Search }, dispatch] = useReducer(
reducer,
{
SearchValue: "",
SearchResult: "",
Search: false,
}
);
const isFirstRender = useRef(true);
const onChangeHandler = (e) =>
dispatch({
type: ACTIONS.UPDATE_SEARCH_VALUE,
payload: { searchValue: e.target.value },
});
useEffect(() => {
if (isFirstRender.current) {
isFirstRender.current = false;
return;
}
dispatch({ type: ACTIONS.START_SEARCHING });
const DispatchQueryByResult = async () => {
const ArrayOfOwners = await FirebaseUtilityInstance.SearchOwnerResult(
SearchValue
);
dispatch({
type: ACTIONS.UPDATE_SEARCH_RESULT,
payload: { searchResult: ArrayOfOwners },
});
dispatch({ type: ACTIONS.STOP_SEARCHING });
return () => {
FirebaseUtilityInstance.SearchOwnerCleanup();
};
};
DispatchQueryByResult();
}, [SearchValue]);
useEffect(() => {
console.log(SearchResult);
}, [SearchResult]);
return {
onChangeHandler: onChangeHandler,
Query: SearchValue,
QueryResult: SearchResult,
isSearching: Search,
};
};
Firebase Method To Do Query:
SearchOwnerResult = async (Query) => {
const { firstName, lastName } = getFirstNameAndLastName(Query);
let ArrayOfOwners = [];
await this.Database()
.ref("users")
.orderByChild("UserType")
.equalTo("owner")
.once("value", (snapshot) => {
const OwnersContainer = snapshot.val();
const keys = Object.keys(OwnersContainer);
for (let i = 0; i < keys.length; i++) {
const CurrentOwner = OwnersContainer[keys[i]];
if (
CurrentOwner.FirstName === firstName ||
CurrentOwner.LastName === lastName
) {
ArrayOfOwners.push(OwnersContainer[keys[i]]);
}
}
});
return ArrayOfOwners;
};

React Native setState not working when I loop through an array

I have a state where I store all the uuids of the posts. I am retrieving the uuids of the posts in useEffect() from firebase and looping through the array of uuids and retrieving all the posts that the user made but the posts state is returning an empty array.
useEffect(() => {
getData();
}, []);
const getData = () => {
setPosts([]);
setPostuuids([]);
firebase
.firestore()
.collection("users")
.doc(uid)
.get()
.then((doc) => {
setPostuuids(doc.data().posts);
});
postuuids.filter((postuuid) => {
firebase
.firestore()
.collection("posts")
.doc(postuuid)
.get()
.then((doc) => {
const image = doc.data().image;
const text = doc.data().text;
const userid = doc.data().uid;
const timestamp = doc.data().timestamp;
const username = doc.data().username;
const uuid = doc.data().uuid;
const name = doc.data().name;
const avatar = doc.data().avatar;
setPosts((prevPosts) => [
...prevPosts,
{
image: image,
timestamp: timestamp,
text: text,
name: name,
userid: userid,
uuid: uuid,
username: username,
avatar: avatar,
},
]);
});
});
};
your postuuids.filter run before request completed, so postuuids is empty.
you should use another useEffect() for postuuids.map():
useEffect(() =>{
postuuids.map((postuuid) => {
firebase
.firestore()
.collection("posts")
.doc(postuuid)
.get()
.then((doc) => {
const image = doc.data().image;
const text = doc.data().text;
const userid = doc.data().uid;
const timestamp = doc.data().timestamp;
const username = doc.data().username;
const uuid = doc.data().uuid;
const name = doc.data().name;
const avatar = doc.data().avatar;
setPosts((prevPosts) => [
...prevPosts,
{
image: image,
timestamp: timestamp,
text: text,
name: name,
userid: userid,
uuid: uuid,
username: username,
avatar: avatar,
},
]);
});
});
};
},[postuuids])
Use map method instead of filter to loop through a list.
Best Regards!

Trying to access items from an array in state

Im trying to access a array in my state in a function. I currently have filled the array like this:
loadFollowing() {
var newFollowing = this.state.following
const db = firebase.firestore()
db.collection('users')
.doc(this.state.uid)
.collection('following')
.get()
.then(snapshot => {
snapshot.forEach(doc => {
var newFollower = {
id: doc.id
}
newFollowing.push(newFollower)
})
this.setState({
following: newFollowing
})
})
then in another function im trying to access elements of this.state.following like this:
loadPosts() {
var newPostList = this.state.postList
var newFollowing = this.state.following
const db = firebase.firestore()
const postCollection = db.collection('posts')
for (let object of newFollowing) {
postCollection
.where('creatorId', '==', object.id)
.get()
.then(snapshot => {
snapshot.forEach(doc => {
var postItem = {
username: doc.data().username,
content: doc.data().content,
creatorId: doc.data().creatorId,
workoutId: doc.data().workoutId,
id: doc.id
}
newPostList.push(postItem)
})
})
this.setState({
postList: newPostList
})
}
But newFollowing turns up empty, does anyone know what to do?
Remove loadPosts() from componentDidMount() and call it once you setState in loadFollowing().
loadFollowing() {
var newFollowing = this.state.following
const db = firebase.firestore()
db.collection('users')
.doc(this.state.uid)
.collection('following')
.get()
.then(snapshot => {
snapshot.forEach(doc => {
var newFollower = {
id: doc.id
}
newFollowing.push(newFollower)
})
this.setState({
following: newFollowing
})
this.loadPosts();
})
}
loadPosts() {
var newPostList = this.state.postList
var newFollowing = this.state.following
const db = firebase.firestore()
const postCollection = db.collection('posts')
for (let object of newFollowing) {
postCollection
.where('creatorId', '==', object.id)
.get()
.then(snapshot => {
snapshot.forEach(doc => {
var postItem = {
username: doc.data().username,
content: doc.data().content,
creatorId: doc.data().creatorId,
workoutId: doc.data().workoutId,
id: doc.id
}
newPostList.push(postItem)
})
})
this.setState({
postList: newPostList
})
}

Resources