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.
Related
I am creating a clothing e-commerce application integrated with woocommerce and Firestore.
I currently am trying to build the 'add to wishlist part, here I am struggling with trying to update the items 'favourite' field in the database.
I present my Firestore"
my Firestore database
I have access to the item on my react native side
I need to be able to iterate through the objects, compare the nested object items ID against the idea of the item I am currently clicking on and change the favorite field to true.
Currently, I have tried to do the following, but to no avail.
const like = (item) => {
// db.collection("users").doc(user).collection("wishlist").doc(random).set({
// id:item.id,
// name:item.name,
// })
db.collection("users")
.doc(user)
.collection("products")
.doc("0")
.get()
.then((data) => {
const info = data.data();
});
};
In order to perform an update to an object that exists in an array-type field, you need to find that particular object first. Unfortunately, there is no way you can query a Firestore collection based on a value that exists in an object that is contained in an array. This kind of filtering cannot be achieved using partial data. To solve this, you have to read the array, find the desired elements that need to be updated, perform the update and then write the document back to Firestore.
I have also written an article called:
How to update an array of objects in Firestore?
so at the end I came up with a solution which I thought I would share here:
const like = (item) => {
const newData = { ...Data };
if (newData !== null) {
let index = newData.Products.findIndex((e) => {
return e.id === item.id;
});
console.log(index);
newData.Products[index].favourite = true;
db.collection("users")
.doc(userID)
.collection("products")
.doc("0")
.set(newData)
.then((data) => setData(data))
.catch((error) => console.log(error));
} else {
alert("An error has occured");
}
};
so this takes the data, saves it, adds the particular change and sets it again
I try to implement cached pagination, in my react app using apollo client.
my query has filter argument, which should be the only argument that create a new key in the cache object.
for some reason, when fetchMore occurs with filter specified, the new data doesn't cause a re-render in the component.
I logged the existing and incoming argument in the merge function, and it seems that for each fetchMore that had filter, new data did arrive. so, i don't understand why the component didn't re-render.
to make things worst: calling fetchMore several times with or without filter send http request and merging the incoming data with the existing data. which i'd expect wouldn't happen as the client should see that it already has a key in the cache for that query with that key argument.
the following is the query:
query Shells($first: Int = 5, $offset: Int = 0, $filter: ShellFilter) {
shells(
orderBy: [STATUS_ASC, EXECUTION_FROM_DESC]
first: $first
offset: $offset
filter: $filter
) {
nodes {
...ShellData
}
totalCount
}
}
the apolloClient config is like this:
const client = new ApolloClient({
cache: new InMemoryCache({
typePolicies: {
Query: {
fields: {
shells: {
keyArgs: ['filter'],
merge: (existing, incoming) => {
console.log('existing:', existing, 'incoming:', incoming);
return mergeObjectsAndNestedArrays<ShellsConnection>(
existing,
incoming,
'nodes',
);
},
},
},
},
},
})
and the component that displays it:
const ControlCenter = () => {
const { showModal } = useModalContext();
const [page, setPage] = useState(1);
const { data, loading, fetchMore } = useShellsQuery();
const [query, setQuery] = useURLQuery();
const onCounterpartiesChange = async (counterparties) => {
await fetchMore({
variables: {
filter: { shellCounterParties: { some: { orgId: { in: '20584' } } } },
},
});
setQuery({ counterparties });
};
const shells = data?.shells?.nodes;
console.log('hello from shells:', shells);
these are the logs:
EDIT 1 - docs reference
Following the docs: https://www.apollographql.com/docs/react/pagination/key-args/#setting-keyargs
any argument can be used as the keyArgs: limit, offset and filter.
In the documentation examples, the arg used as the key is a primitive value, but in your case, the filter arg is an object. This could be causing apollo to see all results as the same cached version. If your data depend only on the orgID I think you could try the nested array notation to set that field as the key.
keyArgs: ["filter", ["shellCounterParties", ["some", ["orgId", ["in"]]]]]
or the custom function
keyArgs: (args, context) => args.filter.shellCounterParties.some.orgId.in
If you really need to cache according to the whole filter object, I guess the simplest way would be stringifying it
keyArgs: (args, context) => JSON.stringify(args.filter)
But to be sure how apollo is caching the data, I highly recommend you to try the apollo devtools
related: https://github.com/apollographql/apollo-client/issues/7314
I think the problem lies where you have defined typePolicies in your code with keyArgs: ['filter'].
Please check official docs:
https://www.apollographql.com/docs/react/caching/cache-configuration/#customizing-cache-ids
You can customize how the InMemoryCache generates cache IDs for individual types in your schema
This is helpful especially if a type uses a field (or fields!) besides id or _id as its unique identifier.
Based on this, you have defined filter as a unique identifier even though that is a variable which is used for filtration purpose. It is not a field to customize the cache but a variable.
Note that these keyFields strings always refer to the actual field names as defined in your schema, meaning the ID computation is not sensitive to field aliases.
My suggestion first of all would be to modify the configuration that you have set up and see if it helps?
Instead of fetchMore use refetch inside useEffect and pass there new variables
function photo({ id }) {
const { data, refetch } = useQuery(GET_PHOTO, {
variables: { id },
});
useEffect(() => {
refetch({ id })
}, [id])
}
so I have a problem right now. I'm entering the users dates into cloud firestore like this:
so this is a user collection, with a document by user's id's and then the dates are entered as a list. But whenever I refresh the page and enter new data, all the previous data disappears.
So I'm wondering how do I enter data so that it goes like collection(userCalendar).doc(USERID).collection(dates) and then it has all the user's data entered as strings rather than an array like I've been doing.
My code for the way it's behaving right now is below. Thank you! :)
export const allEvents = [];
const Calendar = () => {
const [date, setData] = useState([]);
const handleDateClick = async (DateClickArg) => {
if (DateClickArg.jsEvent.altKey) {
const title = prompt("Enter title", DateClickArg.dateStr); // allows user to put a title in
// making object
const event = {
title: title ? title : DateClickArg.dateStr,
start: DateClickArg.date,
allDay: true
}
allEvents.push(event)
const db = fire.firestore();
let currentUserUID = fire.auth().currentUser.uid
const doc = await fire
.firestore()
.collection("userCalendar")
.doc(currentUserUID)
.get()
db.collection("userCalendar")
.doc(currentUserUID)
.set({
activites: allEvents
})
}
}
You can use arrayUnion() to add new items to an array however it'll be difficult for you to query activities of a user.
For example, you cannot fetch a single activity from that array but you'll have to fetch all of them get the required one. Additionally, you cannot update an object in an array directly in Firestore.
Also a document has a max size limit of 1 MB so if a user can have many activities, it'll be best to create sub-collection.
I would recommend restructuring the following way:
users -> { userId } -> activities-> { activityId }
(col) (doc) (col) (doc)
All of user's activities/events are now in a sub-collection "activities" and each activity would be a document instead of an array item. With this you can easily read/update/delete a single activity.
Also checkout: Firestore subcollection vs array
Not sure whether this meets your requirement, but from my understanding you just want to update the activities with the allEvents which contains all the updated activities.
db.collection("userCalendar")
.doc(currentUserUID)
.set({
activites: allEvents
})
should become
db.collection("userCalendar")
.doc(currentUserUID)
.set({
activites: allEvents
}, { merge: true })
Or you can use the update method
db.collection("userCalendar")
.doc(currentUserUID)
.update({
activites: allEvents
})
From the docs
To update some fields of a document without overwriting the entire document, use the update() method:
import { doc, setDoc } from "firebase/firestore";
const cityRef = doc(db, 'cities', 'BJ');
setDoc(cityRef, { capital: true }, { merge: true });
It looks like you're overwriting your collection with every code execution:
db.collection("userCalendar")
.doc(currentUserUID)
.set({
activites: allEvents
})
You should consider to make an array union, so that the values are added to your collection instead of overwriting them:
db.collection("userCalendar")
.doc(currentUserUID)
.update({
activites: firebase.firestore.FieldValue.arrayUnion(
{
allEvents
}),
})
Also some examples from firestore docu:
https://cloud.google.com/firestore/docs/samples/firestore-data-set-array-operations
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
I have a list named 'guests', everything works good until I try to delete a guest from a specific table.
To delete, I fetch the table data from Firestore, filter the user and update it to Firestore again.
When I fetch the data from Firestore, the whole table is added to the ordered 'guests'.
It happens every time I get data and I can't understand how it gets updated because I simply filter the user and update it back to Firestore.
Everything works as expected except this issue, I'm able to delete it from 'tables' but 'guests' has this issue.
I tried few ways to get the data but same result.
Code:
onGuestDelete = async (id, tableId) => {
const { firestore, auth } = this.props;
const data = await firestore.get({ collection: 'guests', doc: auth.uid, subcollections: [{ collection: 'userTables', doc: tableId }] });
const tableGuests = data.get('tableGuests').filter(guest => guest !== id);
firestore.update({ collection: 'guests', doc: auth.uid, subcollections: [{ collection: 'userTables', doc: table.id }] }, { tableGuests });
}
After GET_SUCCESS is fired, you can see the last item of the array (8).
Not sure if I understood you correctly,but try this
onGuestDelete = async (id, tableId) => {
const { firestore, auth } = this.props;
const data = await firestore.get({ collection: 'guests', doc: auth.uid, subcollections: [{ collection: 'userTables', doc: tableId }] });
const tableGuests = data.get('tableGuests');
const filteredGuests = tableGuests.filter(guest => guest !== id);
firestore.update({ collection: 'guests', doc: auth.uid, subcollections: [{ collection: 'userTables', doc: table.id }] }, { filteredGuests });
}
In java, i just put the lists inside onCreate() lifecycle because lifecycle of onStart() always my lists ordered incorrectly. I don't know reactjs, but It might be the same for lifecycle of reactjs