Writing data simultaneously on multiple users, by using their id. firestore - reactjs

I am building a group collaboration application. I have ran into a problem on how to create groups which contain users in it, users are added to the group by their ids.
now the problem is how can a user who creates a group can write data across multiple users simultaneously . so that a group gets created in their database too, they can collaborate and write data on each others database.
it's like group chatting where users collaborate with each-other.
below code shows a user creating a group and trying to write the same group on his friends database, by using an array of user ids.
.doc([firebase.auth().currentUser.uid, newUser1, NewUser2])
firebase
.firestore()
.collection("Groups")
.doc([firebase.auth().currentUser.uid, newUser1, NewUser2])
.collection(AllGroups)
.add({
UpdatedOn: new Date().toString(),
CreatedOn: new Date().toString(),
CreatedBy:firebase.auth().currentUser.name,
users: [array of users]
tasks: [{ array of tasks }]
})
.then((sucess) => {
console.log("done");
})
.catch((err) => {
console.log(err);
});
but its not working please help me out, or please give me a better solution for it.
this is the redux call for calling the function
export const CreatingNewGroup = () => {
let user1id = "***********";
let user2id = "***********";
return async (dispatch) => {
firebase
.firestore()
.collection("Groups")
.doc([firebase.auth().currentUser.uid, user1id, user2id])
.collection("AllGroups")
.add({
UpdatedOn: new Date().toString(),
CreatedOn: new Date().toString(),
CreatedBy: firebase.auth().currentUser.email,
})
.then((sucess) => {
console.log("done");
})
.catch((err) => {
console.log(err);
});
};
};
function is called by this
export default function CreatGroups() {
const Dispatchs = useDispatch();
return (
<View>
<Button
title="Add"
onPress={() => Dispatchs(productsActions.CreatingNewGroup())}
/>
</View>
);
}

The error comes from here:
.doc([firebase.auth().currentUser.uid, newUser1, NewUser2])
The doc function expects a string here, not an array of strings. My guess is that it first tries to split the string on /, and the array you pass doesn't have a split operator.
If you want to write to multiple documents atomically, use a batch write operation.

Related

How do I structure a fetch request after an other?

I have a React blog and I am using Firebase as a back-end. I use createUserWithEmailAndPassword method and after authentication users are redirected to blog where they can start adding blogs.
I store their posts in a firestore collection "posts". No problem with that.
I also want a user object to be created after login with the user preferences. Let's say a specific theme each one has chosen.
I create a collection called "users" at firestore and where I will store each ones object {user: "random#hotmail.com, theme: "dark" ,isAdmin: false, etc} using addDoc method.
I want this object to be created once though and not every time a user logs in.
How do the check on that "users" collection if the user already exists?
I get the collection getDocs(userCollectionRef) and then I filter the data let's say by a property to see if there is that object there.
And if not I want to add the document using addDoc method.
this is the request:
useEffect(() => {
const createUserData = async () => {
const data = await getDocs(usersCollectionRef);
const docs = data.docs.map((doc) => ({
...doc.data(),
id: doc.id,
}));
const userData = docs.filter((doc) => doc.user === user.email);
if (userData.length === 0) {
await addDoc(usersCollectionRef, {
user: user.email,
isAdmin: false,
theme: "dark",
});
}
};
if (user) {
createUserData();
}
}, [user, usersCollectionRef]);
It seems like I am trying
to check and add to the collection at the same time and this is why it doesn't work.
Any ideas?
Should I have an in-between step where I store what I'm getting from the getDocs in a state or something and then do the second request?
Can anyone explain please?
I changed the if statement to a return like so and it worked.
return (
userData.length === 0 &&
(await addDoc(usersCollectionRef, {
user: user.email,
wordGoal: wordGoal,
}))
);
I guess I return the second promise after the first now that's why
I guess when the getDocs request happens because its asynchronous the data then are empty then the code proceeds to the if statement which at that point is true (we have no records) and so executes it.

I have huge reads on my firebase, how to freeze objects for my React Firebase app?

So I have like 30k++ huge data from my firebase. The bad thing about it is, everytime I click back button on my React page, it fetches that 30k++ data again, it hurts the wallet.
Is there anything I can do to solve it?
this is the useEffect I use in my home page
useEffect(() => {
// first 5 posts
Form.allPosts()
.then((res) => {
setSalesLastMonth(res.posts);
setLastKey(res.lastKey);
})
.catch((err) => {
console.log(err);
});
Form.postsToday()
.then((res) => {
setSales(res.posts)
}).catch((err) => {
console.log(err)
})
}, []);
this is an example of the firebase function:
postsToday: async function () {
try {
const yesterday = new Date(new Date().setHours(0, 0, 0, 0));
let today = new Date();
const data = await firestore
.collectionGroup('form')
.orderBy("Created At", "desc")
.where('Created At', '>=', yesterday)
.where('Created At', '<=', today)
.get()
let posts = [];
let lastKey = "";
data.forEach((doc) => {
posts.push({
id: doc.id,
etc etc
});
lastKey = doc.data()['Created At']
});
return { posts, lastKey };
} catch (e) {
console.log(e);
}
},
Can someone help me?
The first thing to do is to enable Firestore's client-side persistence, which means that client will keep a disk-based cache that it can read documents from that it has seen recently.
Another way to reduce the reads is to only load the data the user sees. It seems unlikely that the user will check all 30K forms, so I'd recommend using cursors/queries to implement pagination or infinite scrolling to load only the first page of data initially, and load the rest on demand as needed. You seem to already be tracking the lastKey, which is the main thing you'll need for both.
Finally, also consider if each user needs to perform this query individually, or whether you can perform them once and share the results with all users. For example, you could create a data bundle for things that all users see, serve that bundle from a CDN, and then inject that into the offline cache.

how to save a document to a different collection by reference in firestore in react

This is a problem with either promises or finding a different way to solve the problem.
I have a user, that makes a document about their capabilities e.g.
price:
Bio:
called (contractorPage)
And i have users that look at these.
How do i make it so the users can save these by reference?
using firestore, can you make it so that you add these documents to a collection, but when he original document is changed these are also changed?
my first method is to save the Id of the contractorPage. Which is the user ID (UID) of the firebase auth of the person that made it.
Then To map through these to get all of the saved "id's" documents.
const [ idList, setIdList ] = useState([
])
const [ contractorList, setContractorList ] = useState([
])
useEffect(()=>{
/// IdList has the list of id's
//me.uid is the firebase auth user ID
firestore.collection("SavedId's").doc(me.uid).collection("id").get()
.then( async (querySnapshot) => {
await querySnapshot.forEach(doc => {
console.log(doc.id, " => ", doc.data());
setIdList([...idList, doc.data()]);
console.log("first",idList)
});
console.log("second",idList)
// contractorList has the list
idList.map((data, index) => (
firestore.collection("contractorPages").doc(data.id).get().then((word)=>{
console.log("hi", word.data())
if (word.exists) {
setContractorList([...contractorList, word.data()])
}
})
)
);
})
},[])
This does not work, because the idList.map() function runs before setIdList([...idList, doc.data()]); has finished.
How do I make sure the idList is set before I try to retrieve data from firestore with what is in it?

Lookup multiple Firebase entries using Redux action

I have this Redux action, which looks up a user and returns all the items they have:
export const itemsFetch = () => {
const { currentUser } = firebase.auth();
return dispatch => {
firebase
.database()
.ref(`users/${currentUser.uid}/items`)
.on('value', snapshot => {
dispatch({ type: ITEMS_FETCH_SUCCESS, payload: snapshot.val() });
});
};
};
Works great, and each item returned has a unique key associated with it.
I want to modify this action to look up specific items, which I've done. That works fine too:
export const itemLookup = uid => {
const { currentUser } = firebase.auth();
return dispatch => {
firebase
.database()
.ref(`users/${currentUser.uid}/items/${uid}`)
.on('value', snapshot => {
dispatch({ type: ITEM_LOOKUP_SUCCESS, payload: snapshot.val() });
});
};
};
This also works fine, but I can only use this to lookup a single item.
I want to loop over an array of item ids, and lookup details for each. Doing this from a component, and using mapStateToProps, causes the component to rerender each time, losing the previous lookup in the process.
Is it best to loop over the ids I have at a component level, and make multiple calls. Or should I pass the array to the action, and somehow loop over them within the action?
Thanks
I feel like I'm doing something dumb, or misunderstanding Redux completely.
In my opinion, this is one of the few limitations that firebase has (along side with queries) that sometimes make me want to grow hair again and lose it (I am bald).
I am more experienced with Firestore although I have used Database, but I think you are correct that you can only request one item in Firebase. What I would do to solve this, is to create a new action that receives an array of IDs and then executes and array of promises that will query each doc.
Something like (pseudo code, and you might need to wrap your firebase call into a promise):
let promises = [];
arrayIds.forEach(id => {
promises.push(firebase.database().ref.get(id))
})
return Promise.all(promises).then(dispatch(results))
Now, if you find that the amount of results are usually not a lot, it is totally fine (and usually the way Firebase requires you to) to complete the data filtering in the client.
Using the response from sfratini, I managed to work it out. Here's the action:
export const itemLookup = oid => {
const { currentUser } = firebase.auth();
const items = []
return dispatch => {
new Promise(function (res, rej) {
oid.forEach(id => {
firebase.database().ref(`users/${currentUser.uid}/items/${id}`).on('value', snapshot => {
items.push(snapshot.val())
})
})
}).then(dispatch({ type: ITEM_LOOKUP_SUCCESS, payload: items }))
}
I used the same reducer, and now the items array makes it down to component level. Perfect!

Update a collection with existing values in firebase

I'm developing a react web application with firebase and Now I'm stuck with this problem. So what I want is update the collection with existing data. For example let's say that the following details are already in the collection
org_details: {
general: {
org_name: "ane",
founder: "fng"
},
group:{
admin:{
<details of the admin grp>
},
standard:{
<grp details>
}
}
}
So what I want is add another group details with the existing groups. For an example I need to add a group called "fun" by also having admin and standard group. So this is the firebase query that I've tried.
export const createGroup = (data, history) => async (
dispatch,
getState,
{ getFirestore }
) => {
const firestore = getFirestore();
const { email: userEmail } = getState().firebase.auth;
const groupName = data.groupName;
dispatch({ type: actions.CREATE_GROUP_START });
try {
firestore
.collection("org")
.doc(userEmail)
.update({
"group": {
groupName: {
org_grp_admin: data.org_grp_admin,
org_grp_users: data.org_grp_users
}
}
})
.then(() => {
getOrgData(dispatch, getState, { getFirestore });
history.push("/groupmanagement");
});
dispatch({ type: actions.PROFILE_EDIT_SUCCESS });
} catch (err) {
dispatch({ type: actions.CREATE_GROUP_FAILS, payload: err.message });
}
};
But this query doesn't seem to behave like that I want. It always create a group called "groupName" instead of the group name that is passed from the parameter and always replace the existing data. How should I change the query to get the result that I want?
And I'm using firestore in firebase as the database.
As you are trying to dynamically assign the property key you need to wrap it in [groupName]:{...} to pick up the variable groupName instead of just the string 'groupName'
"group": {
[groupName]: {
org_grp_admin: data.org_grp_admin,
org_grp_users: data.org_grp_users
}
}
As for performing a deep merge (e.g. just update the subgroup if it exists, and if not create the new group without deleting others), this is not possible with the current api however you could either
option 1) Read the data from the database first yourself and manually handle the merge before writing (you could either write your own function or use a package like deepmerge
option 2) Restructure your data to be flatter, for example using a subcollection to store your groups

Resources