NGRX - can't set the state tree as I would like it to be - arrays

So I'm using ngrx for managing the state in my application. I tried to add a new property (selected shifts) which should look like this:
state: {
shifts: {
selectedShifts: [
[employeeId]: [
[shiftId]: shift
]
]
}
}
at the moment, my state looks like this:
state: {
selectedShifts: {
[employeeId]: {
[shiftId]: shift
}
}
}
so as you can see, my "selected shift" is a property, not an array - which makes it diffictult to add/remove/query the state.
How do I compose the state to look like I want it?
This is what I tried in the reducer:
return {
...state,
selectedShifts: {
...state.selectedShifts,
[action.payload.employeeId]: {
...state.selectedShifts[action.payload.employeeId],
[action.payload.shiftId]: action.payload[shift.shiftId]
}
}
};
Now when I try to return the state in the way I'd like to, this is the result:
state: {
selectedShifts: {
[action.payload.employeeId]:
[0]: {[action.payload.shiftId]: { shift }}
}
}
What am I missing here? When I try to replace the {} items which should be [] this error comes up: "," expected.
Oh yea, I would like the index of the array to be the id of the specific shift and not [0], [1]...
Is this possible at all?
Would it be a bad idea to change the index from numerics to the actual shift's id?

Array length kind of miss behaves when you add data at numeric index points. This might get you into problems with array methods using length join, slice, indexOf etc. & array methods altering length push, splice, etc.
var fruits = [];
fruits.push('banana', 'apple', 'peach');
console.log(fruits.length); // 3
When setting a property on a JavaScript array when the property is a valid array index and that index is outside the current bounds of the array, the engine will update the array's length property accordingly:
fruits[5] = 'mango';
console.log(fruits[5]); // 'mango'
console.log(Object.keys(fruits)); // ['0', '1', '2', '5']
console.log(fruits.length); // 6
There is no problem selecting / updating state from object, it's just a bit different from what you're probably used to. With straight hashmap { objectId: Object } finding the required object to update / remove is the fastest possible if changes are defined for object id.
I know your problem is related to NGRX but reading Redux immutable patterns is going to definitely help you out here for add / update / remove objects from the state. https://redux.js.org/recipes/structuring-reducers/immutable-update-patterns
Generally you don't want to have arrays in state ( at least large arrays ) object hashmaps are a lot better.
To get array of your selected user shifts for views you could do something like. Note this is not a shift indexed array just array of shifts under userId property. From original state form following state.
state: {
selectedShifts: {
[employeeId]: {
[shiftId]: shift
}
}
}
const getSelectedShiftsAsArray = this.store.select( getSelectedShifts() )
.map(
userShifts => {
// get array of object ids
const userIds = Object.keys( userShifts );
const ret = {};
for( const userId of userIds ) {
const collectedShifts = [];
// convert Dictionary<Shift> into a Shift[]
// get array of shift ids
const shiftIds = Object.keys( userShifts[userId] );
// map array of shift ids into shift object array
collectedShifts = shiftIds.map( shiftId => userShifts[shiftId] );
// return value for a userId
ret[userId] = collectedShifts;
}
return ret;
});
Code is completely untested and just for a reference one level up from pseudocode. You could easily convert that into a NGRX selector though. The state is there just for the storage, how you model it for use in components is upto selector functions & components themselves.
If you really really need it you could add.
ret[userId].shiftIds = shiftIds;
ret[userId].shifts = collectedShifts;
But it really depends on how you plan to use these.
From my personal experience I would separate shift entities from selectedShifts but how you organise your state is completely up to you.
state: {
shifts: {
// contains shift entities as object property map id: entity
entities: Dictionary<Shift>,
selectedShifts: [
[employeeId]: number[] // contains ids for shifts
]
}
}
Now updating / removing and adding a shift would just be setting updated data into path shifts.entities[entityId]
Also selectedShifts for employeeId would be about checking if id is already in there and appending it into an array if it wasn't. ( If these arrays are humongous I'd go with object hash here too for fast access. <employeeId>: {shiftId:shiftId} ).
Check also:
redux: state as array of objects vs object keyed by id

Related

How to pull all objects from an array of unknown length and place them into a state variable in React?

I imported data from another file (so I technically think it's an object containing an array of objects) called { groupData }. At the moment, the array contains only 5 objects, but it is variable in length and could contain more. Each object looks like this:
{
name: "A Name",
img: "https://imgURL.goes.here",
details: "This is a fun group about fun things.",
likes: 45,
},
My goal is to take each object from the array, modify the data, and place the objects into an empty stateful array called "groups". I want each object to look like this before it goes into the new "groups" array:
{
name: "A Name",
img: "https://imgURL.goes.here",
details: "This is a fun group about fun things.",
hasNotification: Boolean,
userIsAdmin: Boolean,
},
I thought of destructuring the array, but this solution is not scalable if things are going to be added to the array:
const [groupZero, groupOne, groupTwo, groupThree, groupFour] = groupData;
What is the most efficient way to accomplish this? Thank you!!
I am not exactly sure what you need because of the lack of context. But you can use a for loop to iterate through each object in the array.
If it is fine to moderate the existing data:
for (data of groupData){
delete data.likes;
data.hasNotification = true; // or false
data.userIsAdmin = true; // or false
}
setState(groupData); // if you are using useState hooks
If you do not want to change original data:
// create a deep clone of groupData
const newGroup = JSON.parse(JSON.stringify(groupData));
for (data of newGroup){
delete data.likes;
data.hasNotification = true; // or false
data.userIsAdmin = true; // or false
}
setState(newGroup);
Adjust the setState section accordingly if you are using class components

Write data in to nested object in firebase firestore

I have a data structure that looks as follows:
This is the top level of the collection:
I want to write to increment the field count but I can't do it. I've tried so many different methods that I'd rather not go through all of them. The closest I've gotten was through:
const pageRef = admin.firestore().collection("pages").doc(image.page);
pageRef.set(
{
[`images.${image.position}.count`]: admin.firestore.FieldValue.increment(
1
),
},
{ merge: true }
);
But that leaves me with:
Please help. Changing the structure of pages is an option.
This is what I've tried to replicate:
Update fields in nested objects in firestore documents?
The issue is on how the point notaition is being used.
In the Post you shared the example they use is:
var setAda = dbFirestore.collection('users').doc('alovelace').update({
"first.test": "12345"
});
Applying this to your Code and model would be:
const pageRef = admin.firestore().collection("pages").doc(image.page);
pageRef.set(
{
`images[${image.position}].count`: admin.firestore.FieldValue.increment(
1
),
},
{ merge: true }
);
This will affect the element in the Array Images, the element image.position its value count.

Compare two big arrays value for value in Node.js

I have two arrays, one containing 200.000 product objects coming from a CSV file and one containing 200.000 product objects coming from a database.
Both arrays contains objects with the same fields, with one exception: the database objects have a unique ID as well.
I need to compare all 200.000 CSV objects with the 200.000 database objects. If the CSV object already exists in the database objects array I put it in an "update" array together with the ID from the match, and if it doesn't, then I put it in a "new" array.
When done, I update all the "update" objects in the database, and insert all the "new" ones. This goes fast (few seconds).
The compare step however takes hours. I need to compare three values: the channel (string), date (date) and time (string). If all three are the same, it's a match. If one of those isn't, then it's not a match.
This is the code I have:
const newProducts = [];
const updateProducts = [];
csvProducts.forEach((csvProduct) => {
// check if there is a match
const match = dbProducts.find((dbProduct) => {
return dbProduct.channel === csvProduct.channel && moment(dbProduct.date).isSame(moment(csvProduct.date), 'day') && dbProduct.start_time === csvProduct.start_time;
});
if (match) {
// we found a match, add it to updateProducts array
updateProducts.push({
id: match.id,
...csvProduct
});
// remove the match from the dbProducts array to speed things up
_.pull(dbProducts, match);
} else {
// no match, it's a new product
newProducts.push(csvProduct);
}
});
I am using lodash and moment.js libraries.
The bottleneck is in the check if there is a match, any ideas on how to speed this up?
This is a job for the Map collection class. Arrays are a hassle because they must be searched linearly. Maps (and Sets) can be searched fast. You want to do your matching in RAM rather than hitting your db for every single object in your incoming file.
So, first read every record in your database and construct a Map where the keys are objects like this {start_time, date, channel} and the values are id. (I put the time first because I guess it's the attribute with the most different values. It's an attempt to make lookup faster.)
Something like this pseudocode.
const productsInDb = new Map()
for (const entry in database) {
const key = { // make your keys EXACTLY the same when you load your Map ..
start_time: entry.start_time,
date: moment(entry.date),
entry.channel}
productsInDb.add(key, entry.id)
}
This will take a whole mess of RAM, but so what? It's what RAM is for.
Then do your matching more or less the way you did it in your example, but using your Map.
const newProducts = [];
const updateProducts = [];
csvProducts.forEach((csvProduct) => {
// check if there is a match
const key = { // ...and when you look up entries in the Map.
start_time: entry.start_time,
date: moment(entry.date),
entry.channel}
const id = productsInDb.get(key)
if (id) {
// we found a match, add it to updateProducts array
updateProducts.push({
id: match.id,
...csvProduct
});
// don't bother to update your Map here
// unless you need to do something about dups in your csv file
} else {
// no match, it's a new product
newProducts.push(csvProduct)
}
});

How to check an array with regular expression in GraphQL

I need to check the existence of some elements in an array as such
I have an array as such
ar = ['one','two','three']
I want to know how I can individually check the elements in the regular expression code below instead of "/something/" that would map through my array and check if they exist in graphQL one by one.
similar : allCockpitHello (filter: {Association : {value : {regex: "\/something/" }}} limit:2){
nodes{
Name{
value
}
}
You need to have the regex string as an input parameter to be used by the resolver, GraphQL is not going to do the filter for you, you need to do/call that logic in the resolver based on your inputs.
Based on your example, you could have something like this on the schema and resolver:
type Node {
name: String!
}
type NodeQueries {
nodes (filterRegEx :String): [Node]!
}
Once you have the input string on the resolver, the implementation of the filter mechanism is up to you.
const resolvers = {
...
NodeQueries: {
nodes: (parent, params) => {
const {filterRegEx} = params; // regex input string
const ar = ['one','two','three'];
// Create a RegExp based on the input,
// Compare the with the elements in ar and store the result...
// You might end up with ... res = ['one', 'three'];
// Now map the result to match your schema:
return _.map(res, name => ({name}) ); // to end up with [{name: 'one'}, {name: 'three'}]
}
}
...
}
GraphQL is not a magic bullet - it's only a query language, it 'transports' your needs to the engine (local client, remote server ...) where all the necessary processing takes place.
In this case you probably need to pass your array and expression as variables to the server (resolver). If processing is expensive results (similar relation) should be already defined, cached, preprocessed, etc.
If dataset is small you can do this entirely client-side - iterate over an array (fetched using graphql).

Why is the state mutating if I am using spread operator?

I have this code that I am testing on jsfiddle
onVote = (dir, index) => {
console.log(this.state)
const products = [...this.state.products]
products[index].votes = dir ? products[index].votes + 1 : products[index].votes - 1
console.log(this.state.products[index].votes)
// this.setState({products})
};
https://jsfiddle.net/hkL3wug7/2/
However, even though I am not setting State, the console log shows that the state is changes every time I click on the plus and minus signs.
I did the same as in this article https://medium.com/#giltayar/immutably-setting-a-value-in-a-js-array-or-how-an-array-is-also-an-object-55337f4d6702
const newState = [...state] // clone the array
newState[action.index].done = true
return newState
as far as I understand
(it is not duplicate of the other question, I am not asking for an efficient way)
As #Carcigenicate mentioned, you have created a shallow copy of the array which means you have a new array pointing to the same objects in the original.
To avoid mutating the original object, you will need to also create a copy of the one you would like to mutate, e.g.:
// Shallow copy of the array
const products = [...this.state.products];
// Shallow copy of the object within the array
const updatedProduct = { ...products[index] };
// Update the copy of the object
updatedProduct.votes = dir ? updatedProduct.votes + 1 : updatedProduct.votes - 1;
// Replace the object with the updated copy
products[index] = updatedProduct;
As #Carcigenicate mentioned in the comment, using the spread operator creates a shallow copy of the array. This is creating a problem for you because the expanded version of the array contains Objects which are passed by reference. So even though your local variable products is a new copy of this.state.products, they both contain references to the same Objects.
To achieve what you are trying to do, you would have to clone the objects in this.state.products. One possible way to do this is using Object.assign and replace your const products = [...this.state.products] with:
const products = [
Object.assign({}, this.state.products.Orange),
Object.assign({}, this.state.products.Apples),
Object.assign({}, this.state.products.Bananas)
]

Resources