Count arrays with values in state in React - reactjs

I currently have the following state:
this.state = {
selectProduct: [somearrayValues],
quantityProduct: [],
colorsProduct: [somearrayValues],
stockProduct: [somearrayValues],
turnaroundProduct: [],
coatingProduct: [],
attributeProduct: [somearrayValues],
attributeMetaProduct: [somearrayValues],
}
I do a fetch call to fill up the arrays with the needed data.
From here I need to get a count of Arrays that actually contain a value. I'm lost as how to accomplish this.
I first was trying to get to the state with a for each loop but I haven't even got passed this point:
let dropdownArrays = ...this.state;
dropdownArrays.forEach(function(element) {
console.log(element);
});
This gives me an error when babel attempts to compile.
I then tried the below, which returns nothing.
let dropdownArrays = [...this.state];
dropdownArrays.forEach(function(element) {
console.log(element);
});
I know I'm missing it so any help would be greatly appreciated.

Perhaps you could use the Object#values() method to access the array objects (ie the values) of the state, and then count the number of non-empty arrays like so:
// Pre-filled arrays with some values. This solution would work
// regardless of the values you populate the arrays with
const state = {
selectProduct: [1,2,3,4],
quantityProduct: [],
colorsProduct: [4,5,6,7],
stockProduct: [1,2],
turnaroundProduct: [],
coatingProduct: [],
attributeProduct: [6,7,8,9,10],
attributeMetaProduct: [5,4,6],
}
const result = Object.values(state)
.filter((array) => array.length > 0)
.length;
console.log('Number of arrays in state with values (non-empty)', result)

Because state is an object, you instead could use a couple different options. You could do
this.state.values, which will return an array of the values in state.
this.state.values.forEach(function(value) {
console.log(value);
});
Or you could use this.state.entries, which will return an array of the key, value.
this.state.entries.forEach(function(entry) {
console.log(entry);
// expected output [key, value]
});
Lastly as you appear to already be attempting to use destructuring, you can also destructure the result.
this.state.entries.forEach(function([key, value]) {
console.log(key);
console.log(value);
});

you can try with https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries
Object.entries(...this.state)[1].reduce((accumulator, currentValue) => accumulator + currentValue.length, 0)
Bear in mind that to use ...this.state you need https://babeljs.io/docs/en/babel-plugin-proposal-object-rest-spread

var state = {
selectProduct: [1,2,3,4],
quantityProduct: [],
colorsProduct: [4,5,6,7],
stockProduct: [1,2],
turnaroundProduct: [],
coatingProduct: [],
attributeProduct: [6,7,8,9,10],
attributeMetaProduct: [5,4,6],
}
// In your code replace state with this.state
let emptyArrays = Object.values(state).reduce(
(acc, current)=> ( !current.length? ++acc: acc),
0
)
console.log(emptyArrays)

Related

I can't access the props of my objects (Angular)?

I have this variable in my environment.ts:
featureToggle: {"feature1": true, "feature2: false}
In a service of mine, I want to give these values to a component with a getAll-method, just like:
getAll() {
return environment.featureToggle;
}
In a component I'm having an array and call the servicemethod in my ngOnInit, where I assign the values to my array. Through *ngFor im iterating through the array.
Then I get an ERROR NG0901 IterableDiffers.find.
Yes, it might be, because it is an Object Array, so I would have to convert it in my service first to a normal Array, or assign the values to an interface to work with it?
like
interface FeatureInterface {
feature: string,
isActive: boolean;
}
But I can't even .map through my environments variable nor does forEach work. I also tried Object.keys(environment.featureToggle). Is there any way to access and iterate my properties in my environment.ts and work with them in any component?
Component:
features: FeatureInterface[] = [];
ngOnInit(): void {
this.features = this.featureToggleService.getAllFeatures()
Html:
<div *ngFor="let item of features">
{{item.feature}}
...
Check this out!
let featureToggle = { "feature1": true, "feature2": false, "feature3": true};
const result = Object.entries(featureToggle).filter((e) => e[1]).map((i) => i[0]);
console.log(result);
UPDATE 1:
Based on the requirement(as mentioned in the comments), try this:
let featureToggle = { "feature1": true, "feature2": false, "feature3": true };
const result = Object.entries(featureToggle).map((i) => {
return {
feature: i[0],
isAvailable: i[1]
}
});
console.log(result);
[ { feature: 'feature1', isAvailable: true },
{ feature: 'feature2', isAvailable: false },
{ feature: 'feature3', isAvailable: true } ]

updating value of a specific key in redux

I have an array in redux state/store like this [{"answer": "yes", "questionId": 2},{"answer": "yes", "questionId": 3}]
I want to only update answer of an object based on id. like this if this is state and user send {"answer": "no", "questionId": 2}. how to update this state in redux? or just remove this object with questionId of 2 and add the one user sent?
return {
...state,
answers: [...state.answers[0].answer, payload.answer],
};
this does not works
return {
...state,
answers[0].answer: [...state.answers, payload.answer],
};
and this is not permissible.. keeping in mind this array can only store 2 objects. Task is user sends 2 answers and i have to store them in store or array of answers if that answers is not already in array and if it is in array then only update that specific answer.
i tried this also but it does not works as expected.
for (i = 0; i <= state.answers.length; i++) {
if (state.answers[i]?.questionId === payload.questionId) {
state.answers.splice(
state.answers.indexOf(payload),
1,
);
return {
...state,
answers: [...state.answers, payload.answer],
};
} else {
return {
...state,
answers: [...state.answers, payload],
};
}
You can use array.findIndex to find the index of the target object that needs to be updated then replace the object at this index with the new one.
//Make deep copy of state
let tempAnswers = JSON.parse(JSON.stringify(state.answers));
let targetAnswerIndex = tempAnswers.findIndex(answer => {
return answer.questionId === payload.answer.questionId
});
tempAnswers[targetAnswerIndex] = payload.answer;
state.answers = tempAnswers;

How to push array value with defined object in angular

My array
let myArr=["test1","test2"];
Need to add the object like below
let myFinalArr=[["test1":{"val1":"XXX","val2":"YYY"}],["test2":{"val1":"XXX","val2":"YYY"}]];
how to push the data like above in angular.
There are multiple ways to do it. How exactly does the requirement look? Does the object remain same for all the elements in the array?
And expanding from your comment that the following is the actual output's structure
{
"test1": { "val1":"XXX", "val2":"YYY" },
"test2": { "val1":"XXX", "val2":"YYY" }
}
You could try the Array#reduce with spread operator to transform the array
let myArr = ["test1", "test2"];
const output = myArr.reduce((acc, curr) => ({
...acc,
[curr]: { val1: "XXX", val2: "YYY" }
}), Object.create(null));
console.log(output);

ES6: Merge two arrays into an array of objects

I have two arrays that I want to merge together to one array of objects...
The first array is of dates (strings):
let metrodates = [
"2008-01",
"2008-02",
"2008-03",..ect
];
The second array is of numbers:
let figures = [
0,
0.555,
0.293,..ect
]
I want to merge them to make an object like this (so the array items match up by their similar index):
let metrodata = [
{data: 0, date: "2008-01"},
{data: 0.555, date: "2008-02"},
{data: 0.293, date: "2008-03"},..ect
];
So far I do this like so: I create an empty array and then loop through one of the first two arrays to get the index number (the first two arrays are the same length)... But is there an easier way (in ES6)?
let metrodata = [];
for(let index in metrodates){
metrodata.push({data: figures[index], date: metrodates[index]});
}
The easiest way is probably to use map and the index provided to the callback
let metrodates = [
"2008-01",
"2008-02",
"2008-03"
];
let figures = [
0,
0.555,
0.293
];
let output = metrodates.map((date,i) => ({date, data: figures[i]}));
console.log(output);
Another option is to make a generic zip function which collates your two input arrays into a single array. This is usually called a "zip" because it interlaces the inputs like teeth on a zipper.
const zip = ([x,...xs], [y,...ys]) => {
if (x === undefined || y === undefined)
return [];
else
return [[x,y], ...zip(xs, ys)];
}
let metrodates = [
"2008-01",
"2008-02",
"2008-03"
];
let figures = [
0,
0.555,
0.293
];
let output = zip(metrodates, figures).map(([date, data]) => ({date, data}));
console.log(output);
Another option is to make a generic map function which accepts more than one source array. The mapping function will receive one value from each source list. See Racket's map procedure for more examples of its use.
This answer might seem the most complicated but it is also the most versatile because it accepts any number of source array inputs.
const isEmpty = xs => xs.length === 0;
const head = ([x,...xs]) => x;
const tail = ([x,...xs]) => xs;
const map = (f, ...xxs) => {
let loop = (acc, xxs) => {
if (xxs.some(isEmpty))
return acc;
else
return loop([...acc, f(...xxs.map(head))], xxs.map(tail));
};
return loop([], xxs);
}
let metrodates = [
"2008-01",
"2008-02",
"2008-03"
];
let figures = [
0,
0.555,
0.293
];
let output = map(
(date, data) => ({date, data}),
metrodates,
figures
);
console.log(output);
If you use lodash, you can use zipWith + ES6 shorthand propery names + ES6 Arrow functions for a one-liner, otherwise see #noami's answer.
const metrodata = _.zipWith(figures, metrodates, (data, date)=> ({ data, date }));

Replace array item with another one without mutating state

This is how example of my state looks:
const INITIAL_STATE = {
contents: [ {}, {}, {}, etc.. ],
meta: {}
}
I need to be able and somehow replace an item inside contents array knowing its index, I have tried:
return {
...state,
contents: [
...state.contents[action.meta.index],
{
content_type: 7,
content_body: {
album_artwork_url: action.payload.data.album.images[1].url,
preview_url: action.payload.data.preview_url,
title: action.payload.data.name,
subtitle: action.payload.data.artists[0].name,
spotify_link: action.payload.data.external_urls.spotify
}
}
]
}
where action.meta.index is index of array item I want to replace with another contents object, but I believe this just replaces whole array to this one object I'm passing. I also thought of using .splice() but that would just mutate the array?
Note that Array.prototype.map() (docs) does not mutate the original array so it provides another option:
const INITIAL_STATE = {
contents: [ {}, {}, {}, etc.. ],
meta: {}
}
// Assuming this action object design
{
type: MY_ACTION,
data: {
// new content to replace
},
meta: {
index: /* the array index in state */,
}
}
function myReducer(state = INITIAL_STATE, action) {
switch (action.type) {
case MY_ACTION:
return {
...state,
// optional 2nd arg in callback is the array index
contents: state.contents.map((content, index) => {
if (index === action.meta.index) {
return action.data
}
return content
})
}
}
}
Just to build on #sapy's answer which is the correct one. I wanted to show you another example of how to change a property of an object inside an array in Redux without mutating the state.
I had an array of orders in my state. Each order is an object containing many properties and values. I however, only wanted to change the note property. So some thing like this
let orders = [order1_Obj, order2_obj, order3_obj, order4_obj];
where for example order3_obj = {note: '', total: 50.50, items: 4, deliverDate: '07/26/2016'};
So in my Reducer, I had the following code:
return Object.assign({}, state,
{
orders:
state.orders.slice(0, action.index)
.concat([{
...state.orders[action.index],
notes: action.notes
}])
.concat(state.orders.slice(action.index + 1))
})
So essentially, you're doing the following:
1) Slice out the array before order3_obj so [order1_Obj, order2_obj]
2) Concat (i.e add in) the edited order3_obj by using the three dot ... spread operator and the particular property you want to change (i.e note)
3) Concat in the rest of the orders array using .concat and .slice at the end .concat(state.orders.slice(action.index + 1)) which is everything after order3_obj (in this case order4_obj is the only one left).
Splice mutate the array you need to use Slice . And you also need to concat the sliced piece .
return Object.assign({}, state, {
contents:
state.contents.slice(0,action.meta.index)
.concat([{
content_type: 7,
content_body: {
album_artwork_url: action.payload.data.album.images[1].url,
preview_url: action.payload.data.preview_url,
title: action.payload.data.name,
subtitle: action.payload.data.artists[0].name,
spotify_link: action.payload.data.external_urls.spotify
}
}])
.concat(state.contents.slice(action.meta.index + 1))
}

Resources