Cannot Array.filter properly - javascript

Despite of numerous thread on the subject I didn't manage to remove an item of a string based Array using Array.filter method. Here is the filter method in a context of a mutation of a Vuex store.
UPDATE_FILTERS_GEOLOGIES: (state, payload) => {
if (state.filters.geologies.some(elem => elem === payload)) {
state.filters.geologies.filter(elem => !elem.includes(payload))
} else {
state.filters.geologies.push(payload);
}
}
The filter method is call but the item is not removed. I ended up using Array.splice method:
let position = state.filters.geologies.indexOf(payload);
state.filters.geologies.splice(position, 1)
Could you tell me what I'm doing wrong?

based on MDN:
The filter() method creates a new array with all elements that pass the test implemented by the provided function.
so basically what is wrong in your code is that:
state.filters.geologies.filter(elem => !elem.includes(payload))
is not being saved in any variable, thus the filtered array isn't being used. in order to make it work you need to assign the return value of the filter. something like:
state.filters.geologies = state.filters.geologies.filter(elem => !elem.includes(payload))
or as vladislav said:
state.filters.geologies = [...state.filters.geologies.filter(elem => !elem.includes(payload))]

Until splice() method is mutating original array, filter(), map(), or some() methods returns new array and leave the original array intact. So, you can use filter() method, but then you should replace original array with returned array. For example:
UPDATE_FILTERS_GEOLOGIES: (state, payload) => {
let g = state.filter.geologies
let p = payload
g.some(e => e === p)
? (g = { ...g.filter(e => !e.includes(p)) })
: g.push(p)
}

Related

Function just re render one time

I got a problem with this Function. When I trigger this function it only re render the component the first trigger. After that not any more. I cant find the problem :(
function selectAnswer(id, questId) {
let newArr = questions
for(let i = 0; i < newArr.length; i++){
if(questId === newArr[i].id){
const changedAnswers = newArr[i].answers.map(answer => {
return answer.id === id ?
{...answer, selected: !answer.selected} :
{...answer, selected: false}
})
newArr.forEach(element => {
if(questId === element.id){
element.answers = changedAnswers
}
})
}
}
setQuestions(newArr)
}
You're never actually updating the state. This doesn't create a copy of the array, it just duplicates a reference to the array:
let newArr = questions
So this isn't setting state to a new array, but just a reference to the same array:
setQuestions(newArr)
Additionally, instead of creating new state, you are mutating the existing state:
element.answers = changedAnswers
Start by creating a new array, even if it contains the same elements as the original:
let newArr = [...questions];
Then, when you want to modify one of those elements, instead of modifying the existing element you would instead replace it with a new one. So instead of this structure:
newArr.forEach(element => {
});
You could instead replace your new array with a .map() over itself:
newArr = newArr.map(element => {
});
And within that .map() you would return either the unmodified object or the replacement object:
newArr = newArr.map(element => {
if(questId === element.id) {
return {...element, answers: changedAnswers};
} else {
return element;
}
});
Overall, the idea here is that you don't want to loop over an existing array, modify its values, and set that array back to state. That's not "updating state" in the React sense but instead it's "mutating state". Instead, create a new array and populate it with the objects you want. Those objects can be a mix of unchanged objects from the existing array and replaced (not modified) objects from the existing array.

How do the map function that return an object with modified keys? (JS, React.js)

I am trying to create an object with modified keys after map() function (the data is from API), here is my code:
const getData = mySchedule.schedules.map((data) => ({
[data.id]: false,
}));
setCheckId(getData);
This code return:
And I want this output:
Do you have any solution for this case? thank you.
Solution:
Create an object => const getData = {};
Iterate => mySchedule.schedules
Set id as a key and false as value => getData[item.id] = false;
const getData = {};
mySchedule.schedules.forEach(item => {
getData[item.id] = false;
});
setCheckId(getData);
The above answer is correct, but let's dive deep into the problem first:
The map function returns an Array, what you want is an Object. The map function applies the callback for every element and returns a new array of elements modified by the callback function. What are you seeing is the array returned by the map function, note that 1,2,3,4is the index position of the array. Read more about the function map here https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map
The output that you want is an object, but as we have learned before the map function always returns an array with new elements resulting from your map function callback.
If you want an object that are many ways to achieve that, you can loop through the array and add properties to the object.
If you want to understand more and dive into array methods, you can even achieve that using reduce method, therefore the first answer is simpler:
/* Another example using reducer */
const data = [ {10:false,20:false}];
const reducer = (accumulator, currentValue) => currentValue[accumulator] = false;
setCheckId(data.reduce(reducer));

Can my code be made more efficient if I need to update pin based on a condition in javascript?

I have a variable AllPackages which is an array of objects. I want to get all the same Products in all objects in AllPackages and update their pin with the selectedPIN if dp.product.code matches with the object's dealProducts product's code.
setProductPinOnAllPackages(dp): void {
let AllPackages = [{"dealProducts":[{"pin":"","product":{"id":"100","code":"AAA","name":"AAA"}},{"pin":"","product":{"id":"200","code":"BBB","name":"BBB"}}]},{"dealProducts":[{"pin":"","product":{"id":"300","code":"CCC","name":"CCC"}},{"pin":"","product":{"id":"200","code":"BBB","name":"BBB"}},{"pin":"","product":{"id":"400","code":"DDD","name":"DDD"}},{"pin":"","product":{"id":"100","code":"AAA","name":"AAA"}}]}];;
let selectedPIN = dp.pin;
//Can this be made more efficient ????
AllPackages.filter(pkg => pkg.dealProducts
.filter(pkgDealProduct => pkgDealProduct.product.code === dp.product.code)
.map(data => data.pin = selectedPIN));
}
You can make it more efficient by not constructing an unnecessary intermediate array - your goal is to perform side-effects (mutate the data), not to construct a filtered array from the AllPackages, so use a generic iteration method like forEach or for..of instead of .filter in the outer loop.
Similarly, don't use .map, since you aren't looking to map to a new array - again, you're looking for side-effects, so use a generic iteration method.
You can also extract the code from the parameter just once, instead of on every iteration.
const { code, pin } = dp.product;
for (const pkg of AllPackages) {
pkg.dealProducts
.filter(pkgDealProduct => pkgDealProduct.product.code === code)
.forEach((data) => {
data.pin = pin;
});
}

How to fix "Expected to return a value in arrow function array-callback-return"?

I don't understand, are you required to return a specific way through map?
componentDidMount() {
// read the items db
let categories = [];
items.map(cat => {
if (categories.indexOf(cat.category) === -1) {
categories.push(cat.category);
}
});
console.log(categories);
this.setState({ categories: categories });
}
The purpose of .map is to produce a new array from an old one. The return value from your function specifies what the new value at that spot in the array should be. Since you're not returning anything an array of undefined's will be produced. This is probably a mistake, and therefore that lint rule is warning you about it.
In your case, you don't seem to care about the array that map produces at all, so the fix is to use a more appropriate method, such as .forEach
let categories = [];
items.forEach(cat => {
if (categories.indexOf(cat.category) === -1) {
categories.push(cat.category);
}
});
From the documentation on array-callback-return:
Array has several methods for filtering, mapping, and folding. If we forget to write return statement in a callback of those, it's probably a mistake. If you don't want to use a return or don't need the returned results, consider using .forEach instead.
try to use iterator as for or forEach map don't work the purpose from the map it returns
a new array from want to render it may be undefined or
In your case, you don't seem to care about the array that map produces at all, so the fix is to use a more appropriate method, such as .forEach or for
let categories = [];
const items = [];
for (let cat in items) {
if (categories.indexOf(cat.category) === -1) {
categories.push(cat.category);
}
};
console.log(categories);
enter code here
Yes, it requires you to return something to create the new array.
If you just want to iterate items and push some values to categories array, you can use forEach or for...of.
this.setState({
categories: items.filter(cat => categories.indexOf(cat.category) === -1).map(cat => cat.category)
})
Use filter to remove cat with category already in categories, and use map to create a new categories array.

Splice method in React

I'm trying to use splice to add new components into an array. If I use concat all the elements are added properly at the end, but what I also need is add at the beginning or in the middle of the array using splice. Any suggest ?
class App extends React.Component {
state = {
components: []
};
addNewElement = (element) => {
this.setState(prevState => ({
//Works fine
//components: prevState.components.concat(element)
components: prevState.components.splice(0, 0, element)
}));
};
}
splice() returns an array of elements that have been removed from the array. If no elements were removed, splice will return an empty array.
However, splice will change the contents of the array it's called on. You need to set the state on the updated array, not on what splice returns.
Try this method:
addNewElement(element) {
this.state.components.splice(0, 0, element);
this.setState({ components: this.state.components });
}
Below is a working snippet to demonstrate how you can insert a new element at a selected index using splice within a React component.
CodePen Demo
Be careful to note the difference between methods that mutate the array on which they are called and methods which returns mutated versions of the array on which they are called.
prevState.components.splice(0, 0, element) returns a new array containing the elements which have been removed, which for your purposes is going to be nothing.
Notably, splice also mutates the components array; mutating your State elements is A Bad Thing To Do; one simple way to avoid that is to create a clone of your array and splice that.
this.setState(prevState => {
const components = prevState.components.slice(0);
components.splice(0, 0, element);
return { components };
});
This is functional, but relatively inelegant.
Other options you could consider would be to use React's immutability helper or use slice to divide your original array in two then concat all the bits together:
const i = // index at which to insert the new elements
const left = prevState.components.slice(0, i)
const right = prevState.components.slice(i)
return {
components: left.concat(elements, right)
}
Array#splice works in situ and mutates the original array. You have to make a new instance (copy it) with Array#slice and then modify it.
addNewElement = (element) => {
const newArr = prevState.components.slice();
newArr.splice(2, 0, 'foo'); // add 'foo` string at `2nd` index
this.setState(prevState => ({
components: newArr;
}));
};

Categories