nested filter array method - javascript

The purpose of this code is to filter items by color. The filtered is value is not updating to the items that match the color. The filterData function is suppose to filter through the products and then filter through the colors in the array and return the products that are of that color
state = {
filtered:this.props.products,
color:[],
size:'all',
price:'all',
type:'all'
}
change = (e) => {
let value = e.target.value;
let name = e.target.name;
this.setState({
[name]:[...this.state.color,value]
}, () => {
this.filterData();
})
}
filterData = () => {
if (this.state.color) {
var newData = this.props.products.filter(product => {
return this.state.color.filter(c => {
return c === product.colors
})
})
}
this.setState({
filtered:newData
})
}
render() {
let list = this.state.filtered.map(product => {
return(
<div className="product" key={product.product_id}>
<Link to = {{ pathname: '/'+product.product_id}}>
<img className="product_image"src={product.image[0]} />
</Link>
<div className="product_info">
<p className="product_title">{product.title}</p>
<p className="product_price">${product.price}.00</p>
</div>
</div>
)
})

Try changing your filter function, like this:
filterData = () => {
var newData = this.propts.products;
if(this.state.color) {
newData = this.props.products.filter(product => {
return product.colors.some(color => {
return this.state.color.includes(color);
});
})
}
this.setState({
filtered:newData
})
}
Besides fixing the issue of comparing arrays directly, this functions returns true only when at least on of the color of a product is in the list of colors selected by the user.
Of course that you have to account the fact that newData is only defined when this.state.color is set so you should do something about it (I fixed it by assigning a default value).
Update: BTW, your code is awful, you should take some time to format your code accordingly so it is readable.

I think this can be simplified a fair bit. It's hard to tell for sure without seeing what the actually data structure of an item in the color array and what a product looks like. This new filterData method should fix it. This is what I am imagining the data looks like:
this.state.color = ["red","blue","green","purple", ...];
this.state.products = [
{name: "product1", price: 10, color: "orange"},
{name: "product2", price: 20, color: "red"},
...
]
You can also utilize some ES6 features to make your code cleaner and more readable. A big issue is you defined newData within your "if(this.state.color)" block so will always be undefined when it hits "this.setState".
First you should filter the products, and the condition in the filter being if the products color is within the color array. You can achieve this by using "indexOf" and checking if the index is greater than -1. -1 means it does not exist in the array, anything greater is the color's index position, meaning it exists.
filterData = () => {
if(this.state.color){
let { color } = this.state;
let { products } = this.props;
let newData = products.filter(product => {
if(color.indexOf(product.colors) > -1)
return product;
}
this.setState({filtered: newData})
}
}
Based on the example data I provided above, only product2 would be returned since it's color ("red") is included in the color array.
Hope this helps!

Related

Issue when trying to filter array in Vuejs?

data() {
return {
searchString: '',
sortKey: 'name',
checked: false,
Item,
items: [{
price: '1',
name: 'mm'
}, ],
computed: {
computedItems() {
return this.items.map((item, index) => {
item.key = `item_${index}`
return item
})
},
index: function() {
let searchString = this.searchString
let itemsClone = [...this.items] // Change added
const sortedArray = itemsClone.sort((a, b) => {
if (a[this.sortKey] < b[this.sortKey]) return -1
if (a[this.sortKey] > b[this.sortKey]) return 1
return 0
})
if (!searchString) {
return sortedArray
} else {
searchString = searchString.trim().toLowerCase()
const search_array = sortedArray.filter((items) => {
if (items.name.toLowerCase().indexOf(searchString) !== -1) {
return items
}
})
return search_array
}
}
}
<div class="wrapper">
<input
type="text"
v-model="searchString"
placeholder="search items from here"
/>
<br />
<virtual-list
class="list"
style="height: 360px; overflow-y: auto"
data-key="key"
:keeps="20"
:data-sources="computedItems"
:data-component="Item"
/>
<hr />
</div>
Issue when trying to filter array in Vuejs?
I am able to render list of items, but issue is unable to filter the array file. I have taken v-model inside of my input search field, and then writing computed property to it, But still i am getting error
Can i use v-model inside of my search input and filter the data???
Check your .filter() function
Check the "Problems" tab to the right of the console at the bottom right:
Expected to return a value at the end of arrow function. (array-callback-return)
The implementation looks like this:
const search_array = sortedArray.filter((items) => {
if (items.name.toLowerCase().indexOf(searchString) !== -1) {
return items
}
})
So the filter function would work like this:
const search_array = sortedArray.filter((item) => {
return item.name.toLowerCase().indexOf(searchString) !== -1;
});
You are supposed to return true if the item should be kept, not the item itself.
JavaScript will assume items is a true-ish value and that is valid code. It is just an eslint warning, but an important warning here.
See documentation of .filter():
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
Make your search work
You simply forgot to use the correct search value variable.
You named it filterValue in data, but index uses this.searchString.
So fixing this in the first line of index():
let searchString = this.filterValue
If you output {{ index }} in your template, you can see your filtered array in real time while typing.
Updated sandbox with code to filter items based on inputs.
computedItems() {
let initItems = this.items.map((item, index) => {
item.key = `item_${index}`
return item
})
return initItems.filter((item) => item.name.includes(this.filterValue))
},

How to change update an object within a useState array

Suppose we have an array of objects in userInformation:
[
{
firstName:'Bob',
lastName:'Dude',
},
{
firstName:'John',
lastName:'Rad',
}
]
const [userInformation, setUserInformation] = useState([]);
userInformation.forEach((user, index) => {
if (snapshot.val().leadID === user.leadID) {
setUserInformation((userInformation) => ({
...userInformation,
[index]: snapshot.val(),
}));
}
});
I would like to update the second object.
My code doesn't seem to be working quite right. Any suggestions?
Yes few suggestions I have for you:)
First of all you have never assigned userinformation to your state.
So it should be some what like below
const [userInformation, setUserInformation] = useState(userinformation);
Or you must be getting userinformation from an API and using useEffect to initialize it.
Second thing is you must have an id as well as index key on each user something like this:
[
{
leadId:1,
firstName:'Bob',
lastName:'Dude',
index:1
},
{
leadId:2,
firstName:'John',
lastName:'Rad',
index:2
}
]
Now, Coming to what you are expecting you can use map() function in such as way that once the condition is met, you update that particular user, otherwise you should return back same users when condition is not met.
const updatedUsers = userInformation.map((user, index) => {
if (snapshot.val().leadID === user.leadID) {
return setUserInformation((userInformation) => ({
...userInformation,
[index]: snapshot.val(),
}));
}
return userInformation;
});
Here I think a simple find() would do the trick, rather than trying to loop the array.
const updateInfo = (id, obj /* snapshot.val() */) => {
const item = userInformation.find(({ leadID }) => leadID === id);
const updatedItem = {
...item,
...obj
};
setUserInformation((previousInfo) => {
...userInformation,
...updatedItem
})
}
Sorry for the lack of information provided in my question.
I was using firebase realtime with React, and not wrapping my logic in a useEffect was one problem.
I went with this solution:
setUserInformation(
userInformation.map((user) =>
user.leadID === snapshot.val().leadID
? {
...user,
...snapshot.val(),
}
: user
)
);
Thank you for all your answers!

React - Check for duplicates in an array

I'm working on simple list where you can simply add your words to the list.
Main problem is duplicates, I tried many solutions but they weren't even close.
state = {
people: [{ name: null, count: null }]
}
handleSubmit = (e) => {
this.setState(({ count }) => ({
count: count + 1
}));
this.props.addHuman(this.state);
}
addHuman = (human) => {
let people = [...this.state.people, human];
this.setState({
people: people
});
}
I hope for solution which will check if there is any duplicate already in the array
You could make a check if there is someone with the same name already in the array. A better property to check would be an email adresse.
find takes a callback function as parameter. Inside this function, I compare the name properties. If it's a match, find returns true, then a do an early return in the next line and the person isn't added.
addHuman = (human) => {
const exists = this.state.people.find(p => p.name === human.name);
if (exists) return;
let people = [...this.state.people, human];
this.setState({
people: people
});
}
https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/Array/find
if you want to de duplicate the array...you can do it in this way
cont array = [1,1.2,3,5,5];
[...new Set(array)]
This will work as well, there are plenty of ways to achieve the desired outcome.
addHuman = human => {
const updatedPeople = people.includes(human) ? people : [ ...people, human ];
this.setState({
people: updatedPeople
});
}
Remove_Duplicate_recs(filteredRecord)
{
var records = [];
for (let record of filteredRecord)
{
const duplicate_recorde_exists = records.find(r => r.MovementId ===
record.MovementId);
if (duplicate_recorde_exists)
{
continue ;
}
else
{
records.push(record);
}
}
return records;
}

Do I need to use PrevState even if I spread the state into a variable?

I am testing some code to try and understand the race condition regarding the use of setState().
my code can be found here
my code below:
import React from "react";
export default class App extends React.Component {
state = {
id: "",
ids: [{ id: 7 }, { id: 14 }]
};
// here is where I create the id numbers
uniqueIdCreatorHandler = incrementAmount => {
let ids = [...this.state.ids];
let highestId = 0;
if (ids.length > 0) {
highestId = ids
.map(value => {
return value.id;
})
.reduce((a, b) => {
return Math.max(a, b);
});
}
let newId = highestId + incrementAmount;
ids.push({ id: newId });
this.setState({ ids: ids });
};
idDeleterHanlder = currentIndex => {
let ids = this.state.ids;
ids.splice(currentIndex, 1);
this.setState({ ids: ids });
};
//below is when I test performing the function twice, in order to figure if the result would be a race condition
double = (firstIncrementAmount, secondIncrementAmount) => {
this.uniqueIdCreatorHandler(firstIncrementAmount);
this.uniqueIdCreatorHandler(secondIncrementAmount);
};
render() {
let ids = this.state.ids.map((id, index) => {
return (
<p onClick={() => this.idDeleterHanlder(index)} key={id.id}>
id:{id.id}
</p>
);
});
return (
<div className="App">
<button onClick={() => this.uniqueIdCreatorHandler(1)}>
Push new id
</button>
<button onClick={() => this.double(1, 2)}>Add some Ids</button>
<p>all ids below:</p>
{ids}
</div>
);
}
}
when invoking the double function on the second button only the secondIncrementAmount works. You can test it by changing the argument values on the call made on the onClick method.
I think that I should somehow use prevState on this.setState in order to fix this.
How could I avoid this issue here? This matter started at CodeReview but I did not realize how could I fix this.
There is also a recommendation to spread the mapped ids into Math.max and I could not figure out how and Why to do it. Isn't the creation of the new array by mapping the spreaded key values safe enough?
.splice and .push mutate the array. Thus the current state then does not match the currently rendered version anymore. Instead, use .slice (or .filter) and [...old, new] for immutable stateupdates:
deleteId = index => {
this.setState(({ ids }) => ({ ids: ids.filter((id, i) => i !== index) }));
};
uniqueIdCreatorHandler = increment => {
const highest = Math.max(0, ...this.state.ids.map(it => it.id ));
this.setState(({ ids }) => ({ ids: [...ids, { id: highest + increment }] }));
};
setState can be asynchronous, batching up multiple changes and then applying them all at once. So when you spread the state you might be spreading an old version of the state and throwing out a change that should have happened.
The function version of setState avoids this. React guarantees that you will be passed in the most recent state, even if there's some other state update that you didn't know about. And then you can product the new state based on that.
There is also a recommendation to spread the mapped ids into Math.max and I could not figure out how and Why to do it
That's just to simplify the code for finding the max. Math.max can be passed an abitrary number of arguments, rather than just two at a time, so you don't need to use reduce to get the maximum of an array.
uniqueIdCreatorHandler = incrementAmount => {
this.setState(prevState => {
let ids = [...prevState.ids];
let highestId = Math.max(...ids.map(value => value.id));
let newId = highestId + incrementAmount;
ids.push({ id: newId });
this.setState({ ids: ids });
});
};
This isn't the most elegant solution but you can pass a callback to setState(see https://reactjs.org/docs/react-component.html#setstate).
If you modify uniqueIdCreatorHandler like this:
uniqueIdCreatorHandler = (incrementAmount, next) => {
let ids = [...this.state.ids];
let highestId = 0;
if (ids.length > 0) {
highestId = ids
.map(value => {
return value.id;
})
.reduce((a, b) => {
return Math.max(a, b);
});
}
let newId = highestId + incrementAmount;
ids.push({ id: newId });
this.setState({ ids: ids }, next); //next will be called once the setState is finished
};
You can call it inside double like this.
double = (firstIncrementAmount, secondIncrementAmount) => {
this.uniqueIdCreatorHandler(
firstIncrementAmount,
() => this.uniqueIdCreatorHandler(secondIncrementAmount)
);
};

Which approach in React is better?

Below both code does exactly same but in different way. There is an onChange event listener on an input component. In first approach I am shallow cloning the items from state then doing changes over it and once changes are done I am updating the items with clonedItems with changed property.
In second approach I didn't cloned and simply did changes on state items and then updated the state accordingly. Since directly (without setState) changing property of state doesn't call updating lifecycles in react, I feel second way is better as I am saving some overhead on cloning.
handleRateChange = (evnt: React.ChangeEvent<HTMLInputElement>) => {
const {
dataset: { type },
value,
} = evnt.target;
const { items } = this.state;
const clonedItems = Array.from(items);
clonedItems.map((ele: NetworkItem) => {
if (ele.nicType === type) {
ele.rate = Number(value);
}
});
this.setState({ items: clonedItems });
};
OR
handleRateChange = (evnt: React.ChangeEvent<HTMLInputElement>) => {
const {
dataset: { type },
value,
} = evnt.target;
const { items } = this.state;
items.map((ele: NetworkItem) => {
if (ele.nicType === type) {
ele.rate = Number(value);
}
});
this.setState({ items });
};
You can use this
this.setState(state => {
const list = state.list.map(item => item + 1);
return {
list,
};
});
if you need more info about using arrays on states, please read this: How to manage React State with Arrays
Modifying the input is generally a bad practice, however cloning in the first example is a bit of an overkill. You don't really need to clone the array to achieve immutability, how about something like that:
handleRateChange = (evnt: React.ChangeEvent<HTMLInputElement>) => {
const {
dataset: { type },
value,
} = evnt.target;
const { items } = this.state;
const processedItems = items.map((ele: NetworkItem) => {
if (ele.nicType === type) {
return {
...ele,
rate: Number(value)
};
} else {
return ele;
}
});
this.setState({ items: processedItems });
};
It can be refactored of course, I left it like this to better illustrate the idea. Which is, instead of cloning the items before mapping, or modifying its content, you can return a new object from the map's callback and assign the result to a new variable.

Categories

Resources