Update a collection with existing values in firebase - reactjs

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

Related

Import data from API to Algolia

I am trying to push data from MongoDB to Algolia using Redux, and it IS importing data. However, it is not importing data into individual array, but rather the whole object.
Here's what I mean:
How would I extrapolate each individual array?
const passwordList = useSelector((state) => state.passwordList);
const { loading, error, passwords } = passwordList;
useEffect(() => {
dispatch(listPasswords());
}, [dispatch]);
const objects = [{ passwords }];
index
.saveObjects(objects, { autoGenerateObjectIDIfNotExist: true })
.then(({ objectIDs }) => {
console.log(objectIDs);
});
saveObjects takes in an array of objects
const objects = [{ passwords }]; will create a new array with only 1 object that's why it shows only 1 record.
[{
objectID: 1234 //created from autoGenerateObjectIDIfNotExist: true
passwords: [{ ... }, { ... }]
}]
Since your passwords is already an array of password objects you can directly pass it to the saveObjects and it will create individual record for each element in the array
index
.saveObjects(passwords, { autoGenerateObjectIDIfNotExist: true })
.then(({ objectIDs }) => {
console.log(objectIDs);
});
PS. it is recommended to have objectID defined instead of auto generating. I have come across issues where records get duplicated when auto generated object IDs are used when indexing large number of records at a time.
Also it is not recommended to index sensitive information.

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.

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

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.

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!

Pass variables to redux

I'm trying to pass two variables (strings) into a redux action. The purpose of this is the component knows the ID of the Company Profile it's trying to look up and we are getting that data using redux because this pattern is used in a couple places.
In the component:
componentWillMount() {
this.props.actions.getObject('Company', '16747fce-f0b1-422b-aa60-220c2dccac58')
}
In the action:
export function getObject(dataClass, dataId) {
return {
'BAQEND': {
types: [
GET_OBJECT,
GET_OBJECT_SUCCESS,
GET_OBJECT_FAILURE
],
payload: (db) => db.(dataClass).load(dataId)
}
}
}
The dataClass is Company and the dataID is a guid.
My issue is I can't get the dataClass variable in there w/o throwing an error. dataID works great. If I were to change payload: (db) => db.Company.load(dataId) it works.
I've tried a couple things that did not work:
payload: (db) => db.dataClass.load(dataId)
payload: (db) => db.`${dataClass}`.load(dataId)
`payload: (db) => db.${dataClass}.load(dataId)`
If i understand correctly, db is an object which holds a key (property) of Company
db = {
Company: 'someGuid'
}
If this is the case, then you can use the Property accessors like this:
db['Company'] Or db[dataClass]

Resources