Hello fellow programmers, I'm new & really noob to React Native so I'll quickly summarize the example & the code before we move on..
I'm fetching a list of products from an api and storing it in two arrays: products[] and catalogue[]
Catalogue array is used to store all the items received from the api. The products array is what will be used by the Flatlist to display the data. When filter is applied, the items in products should get filtered so that the Flatlist with also update the newly filtered data on the screen.
Now here is a simplified one object out of all the objects data that I'm fetching from the api.
Object {
"designNumber": "CHE-S-207 RW",
"id": 292187,
"imageUrl": "http://company.shop.org/images/shop/136/4.jpg",
"itemCategory": "BRACELET",
"itemStatus": "INSTOCK",
"itemType": "CHE",
"quantity": "1",
"rfidTag": "Item4",
"shopId": 136,
"skuNumber": "Item4",
}
The main values that I'll be using for my filter from the object's data are itemStatus, itemCategory and itemType.
I'm using these variables in my state object to store the value inside them which I will be picking from the Filter Screen UI using Multiple selection Pickers.
state = {
products: [],
catalogue: [],
itemStatus: "", //Binary values. Only one string value for this one
itemCategory: [], //Multiple values in array
itemType: [], //Multiple values in array
};
Now we come to the main deal. Let's assume I've selected certain values for these filter parameters using the filter screen UI. For example, itemStatus: "AVAILABLE",itemCategory: ["MENS", "WOMENS", "CORPORATE"] and itemType: ["COMMON","UNCOMMON", "SALE", "EXCLUSIVE"]
So in layman's term, using the UI I selected Available for Status, 3 values for Category and 4 values for Type. Now in the end there is an apply filter button. This is where I call to a function like filterProducts() which will take these multiple parameters and filter my Flatlist items accordingly.
Remember, the catalogue[] array has the entire data fetched from the api, and the products[] array is what getting displayed in the Flatlist. So ideally whatever new filtered data that will be coming from the filter function should update the products[] array so that I think will update on the Flatlist as well.
My issue? Every post that I've read on stack and medium always only looks into the Searchbar Search filter example. Which is basically only accepting 1 filter string and then filtering the list.
But what if I have multiple parameters to filter like in the example I'm trying to achieve? what if you have 3 parameters, and each parameter can have multiple values (4 or more) to be considered in filtering. How to apply such a complex level of filtering, and then return the filtered items to my Flatlist?
<Button title="Apply Filter" onPress={() => this.filterProducts}/>
filterProducts = () => {
//Logic to take all the filter parameters and their selected values
this.state.products = if({/** No filters are applied */}){
return this.state.catalogue //Return original data
} else {
this.state.catalogue.filter({/** Apply complex filter logic here}).map(){
//return filtered items list
}
}
}
maybe the logic flow is like this? Except I don't know how to filter a list with these many filter parameters present at once. Any help will greatly appreciated. Thanks.
Update 1: Based on u/TommyLeong's suggestion, I have updated my code. Here's how my filter function and the logic looks like:
state = {
isFilterActive: false,
products: [],
catalogue: [],
itemStatus: "",
itemCategory: [],
itemType: [],
}
//Get data from the api
fetchProducts = aysnc () => {
//Make the api call and get the response
const data = await response.json();
this.setState({
products: data.data, //Originally show all the data from the api
catalogue: data.data, //Store all the data from the api
});
};
//Filter Function
filterProducts = () => {
console.log("function called", this.state.isFilterActive);
//Pack all filter data in one query object
let query = {
itemStatus: this.state.itemStatus,
itemCategory: this.state.itemCategory,
itemType: this.state.itemType,
};
console.log("Filter object", Object.entries(query));
if (this.state.isFilterActive) {
const filteredData = this.state.catalogue.filter(function (item) {
return Object.entries(query).every(([key, value]) =>
value.includes(item[key])
);
});
console.log("Result: Filtered Array", filteredData);
this.setState({ products: filteredData });
} else {
//Return original data is isFilterActive == false
this.setState({ products: this.state.catalogue });
}
};
render(){
return(
//Contains my views. My Filter screen has a bunch of pickers
//and a apply filter button
<DropDownPicker
label="Item Status"
multipleSelection={true} //More than one values can be selected
items={/*define my filter values for itemStatus here*/}
onChangeItem={(item) => {
this.setState({ itemStatus: item.value});
}
/>
<DropDownPicker
label="Item Category"
items={/*define my filter values for itemCategory here*/}
onChangeItem={(item) => {
//item here is an array of selected values
this.setState({ itemCategory: item});
}
/>
<DropDownPicker
label="Item Type"
multipleSelection={true} //More than one values can be selected
items={/*define my filter values for itemType here*/}
onChangeItem={(item) => {
//item here is an array of selected values
this.setState({ itemType: item });
}
/>
<Button
title="Apply Filter"
onPress={() => {
this.setState({ isFilterActive: true }, () =>
this.filterProducts()
);
}}
)
}
Now that my code and logic is fairly clear, let's move onward to the new problem.
the filter logic in the filter function 100% works! But only when all 3 filter properties have some value selected
//my filter object in the function
let query = {
itemStatus: this.state.itemStatus,
itemCategory: this.state.itemCategory,
itemType: this.state.itemType,
};
Case 1: console.log when all 3 filter categories have some values selected:
Filter object Array [
Array [
"itemStatus",
"INSTOCK",
],
Array [
"itemCategory",
Array [
"MENS",
"WOMENS",
],
],
Array [
"itemType",
Array [
"COMMON",
"SALE"
],
],
]
Result: Filtered Array Array[
Object: {
//first item matching all 3 filter criteria
},
Object: {
//2nd item matching all 3 filter criteria
},
Object: {
//3rd item matching all 3 filter criteria
},
]
Case 2: console.log when no value is selected for any one of the 3 filter criterias. For this case, I pick itemType i.e. itemType[] is blank.
Filter object Array [
Array [
"itemStatus",
"INSTOCK",
],
Array [
"itemCategory",
Array [
"MENS",
"WOMENS",
],
],
Array [
"itemType",
Array [], //Array is blank here because no filter
//values were picked for itemType
],
]
Result: Filtered Array Array [] //No items returned to me, Flatlist becomes empty
I certainly know that the issue is in this filter logic, but I can't scratch my head off, why the filter is not working when there are undefined values for one of the 3 criteria.. and most importantly, again idk how to fix it :(
//Current filter Logic
if (this.state.isFilterActive) {
const filteredData = this.state.catalogue.filter(function (item) {
return Object.entries(query).every(([key, value]) =>
value.includes(item[key])
);
});
console.log("Result: Filtered Array", filteredData);
this.setState({ products: filteredData });
} else {
//Return original data is isFilterActive == false
this.setState({ products: this.state.catalogue });
}
This is how I will tackle..
Create a variable call hasFilterApply to store boolean value on whether user has applied any filter. You need to set boolean back to FALSE when remove all the filters from UI.
Every time before filtering, I will decide which array (catalogues/products) to do filter based on hasFilterArray. If true, means I should continue to use this.state.products as it contains the latest filtered product list.
When rendering, check again whether hasFilterApply is TRUE. If yes, then render this.state.products otherwise just render this.state.catalgoues
this.state = {
hasFilterApply: false, // update hasFilterApply to TRUE when one or more filter is applied, set to FALSE when no filter is applied
catalogues: [], // keep original response
products: [] // this will be the final render product list
}
/*
* Assuming this is how your catalgoues will be
const catalogues = [{"designNumber":"CHE-S-207 A","id":1,"imageUrl":"http://company.shop.org/images/shop/136/4.jpg","itemCategory":"BRACELET","itemStatus":"AVAILABLE","itemType":"EXCLUSIVE","quantity":"1","rfidTag":"Item1","shopId":1,"skuNumber":"Item1",},{"designNumber":"CHE-S-207 B","id":2,"imageUrl":"http://company.shop.org/images/shop/136/4.jpg","itemCategory":"MENS","itemStatus":"NO","itemType":"SALE","quantity":"1","rfidTag":"Item2","shopId":2,"skuNumber":"Item2",},{"designNumber":"CHE-S-207 C","id":3,"imageUrl":"http://company.shop.org/images/shop/136/4.jpg","itemCategory":"WOMENS","itemStatus":"AVAILABLE","itemType":"UNCOMMON","quantity":"1","rfidTag":"Item3","shopId":3,"skuNumber":"Item3",},{"designNumber":"CHE-S-207 D","id":4,"imageUrl":"http://company.shop.org/images/shop/136/4.jpg","itemCategory":"CORPORATE","itemStatus":"AVAILABLE","itemType":"COMMON","quantity":"1","rfidTag":"Item4","shopId":4,"skuNumber":"Item4",},{"designNumber":"CHE-S-207 E","id":5,"imageUrl":"http://company.shop.org/images/shop/136/4.jpg","itemCategory":"MENS","itemStatus":"AVAILABLE","itemType":"COMMON","quantity":"1","rfidTag":"Item5","shopId":5,"skuNumber":"Item5",},]
*/
getFromAPI = async () => {
const catalogues = await getCatalogues();
this.setState({ catalogues })
}
filterStatus = (filterValue) => {
const latestList = hasFilterApply ? this.state.products : this.state.catalogues;
const newList = []
latestList.filter((product)=>{
if(product.itemStatus === filterValue){
newList.push(product)
}
}
this.setState({ products: newList })
}
filterCategory = (filterValue) => {
const latestList = hasFilterApply ? this.state.products : this.state.catalogues;
const newList = []
latestList.filter((product)=>{
if(product.itemCategory === filterValue){
newList.push(product)
}
}
this.setState({ products: newList })
}
filterType = (filterValue) => {
const latestList = hasFilterApply ? this.state.products : this.state.catalogues;
const newList = []
latestList.filter((product)=>{
if(product.itemType === filterValue){
newList.push(product)
}
}
this.setState({ products: newList })
}
renderMyProducts = () => {
// At the end before you decide render the products, you should know which ARRAY (catalgoues/products) should be passed to your component.
// Do checking on whehter "hasFilterApply" is TRUE or false. If false, then pass catalgoues, otherwise pass products to your component.
}
render(){
return(
// render your components
)
}
So I have an array of names:
const names = ['student1', 'student2', 'student3']
and I have an array of attendance objects:
const attendance = [
{student1: ['On Time', 'Late']},
{student2: ['Late', 'Late']},
{student3: ['On Time', 'Excused']},
]
I wanted to find the student object in the attendance array based off the names from the names array.
So currently I have:
names.forEach(person => {
function attendanceData(p) {
return Object.keys(attendance).toString() == p.toString()
}
console.log(attendance.find(attendanceData(person)))
})
However, this gives me an error saying:
Uncaught (in promise) TypeError: false is not a function
The next stack says "at Array.find()"
I'm wondering how I'm not using this correctly and if there was a better way to do this, what should I do?
I believe this is what you want. Your data is structured a little strangely though, so I would like to know on a grander scale what you want this code to do.
const findStudentAttendance = (att, studentName) => {
return att.find(obj => Object.keys(obj)[0] === studentName)
}
names.forEach(name => {
console.log(
findStudentAttendance(attendance, name)
)
}) /* =>
{ student1: [ 'On Time', 'Late' ] }
{ student2: [ 'Late', 'Late' ] }
{ student3: [ 'On Time', 'Excused' ] }
*/
try it
let result = attendance.filter(obj => names.includes(Object.keys(obj)[0]))
console.log(result)
Use array's method "filter" for getting items. You need get keys of object and take first key. And then you'll can check presence in names array, via array's method "includes"
more information about this methods here:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Filter
Okay, so I am trying to create a function that allows you to input an array of Objects and it will return an array that removed any duplicate objects that reference the same object in memory. There can be objects with the same properties, but they must be different in-memory objects. I know that objects are stored by reference in JS and this is what I have so far:
const unique = array => {
let set = new Set();
return array.map((v, index) => {
if(set.has(v.id)) {
return false
} else {
set.add(v.id);
return index;
}
}).filter(e=>e).map(e=>array[e]);
}
Any advice is appreciated, I am trying to make this with a very efficient Big-O. Cheers!
EDIT: So many awesome responses. Right now when I run the script with arbitrary object properties (similar to the answers) and I get an empty array. I am still trying to wrap my head around filtering everything out but on for objects that are referenced in memory. I am not positive how JS handles objects with the same exact key/values. Thanks again!
Simple Set will do the trick
let a = {'a':1}
let b = {'a': 1,'b': 2, }
let c = {'a':1}
let arr = [a,b,c,a,a,b,b,c];
function filterSameMemoryObject(input){
return new Set([...input])
}
console.log(...filterSameMemoryObject(arr))
I don't think you need so much of code as you're just comparing memory references you can use === --> equality and sameness .
let a = {'a':1}
console.log(a === a ) // return true for same reference
console.log( {} === {}) // return false for not same reference
I don't see a good reason to do this map-filter-map combination. You can use only filter right away:
const unique = array => {
const set = new Set();
return array.filter(v => {
if (set.has(v.id)) {
return false
} else {
set.add(v.id);
return true;
}
});
};
Also if your array contains the objects that you want to compare by reference, not by their .id, you don't even need to the filtering yourself. You could just write:
const unique = array => Array.from(new Set(array));
The idea of using a Set is nice, but a Map will work even better as then you can do it all in the constructor callback:
const unique = array => [...new Map(array.map(v => [v.id, v])).values()]
// Demo:
var data = [
{ id: 1, name: "obj1" },
{ id: 3, name: "obj3" },
{ id: 1, name: "obj1" }, // dupe
{ id: 2, name: "obj2" },
{ id: 3, name: "obj3" }, // another dupe
];
console.log(unique(data));
Addendum
You speak of items that reference the same object in memory. Such a thing does not happen when your array is initialised as a plain literal, but if you assign the same object to several array entries, then you get duplicate references, like so:
const obj = { id: 1, name: "" };
const data = [obj, obj];
This is not the same thing as:
const data = [{ id: 1, name: "" }, { id: 1, name: "" }];
In the second version you have two different references in your array.
I have assumed that you want to "catch" such duplicates as well. If you only consider duplicate what is presented in the first version (shared references), then this was asked before.
I have several objects and i would like to get one and check a specific property
so i have
data: [{is_guest: true},{permission:'is_allowed_ip'}]
Now when i check the console.log(route.data) am getting
0:{is_guest:true},
1:{permission:'is_allowed_ip' }
and typeof route.data is an object
now i would like to get the object with is_guest:true
So i have tried
const data = Object.keys(route.data).map((index) => {
if (route.data[index].is_guest) {
return route.data[index]
}
});
console.log("route data is",data) //this still returns all the items
But the above fails returning all the objects.
How do i loop through all the objects and retrieve just only one with the is_guest key and value true
Sounds like you want Object.values, not Object.keys, and filter:
const data = Object.values(route.data).filter(e => e.is_guest);
Object.values is fairly new, but present on up-to-date Node, and entirely polyfillable.
Example:
const route = {
data: [
{is_guest: true},
{permission:'is_allowed_ip'}
]
};
const data = Object.values(route.data).filter(e => e.is_guest);
console.log(data);
Using E6:
data.filter(o => o.is_guest)
You can use the filter method.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
I added some ids into your array just to make easier to understand.
// added ids to exemplify
const data = [
{id: 1, is_guest: true},
{id: 2, permission:'is_allowed_ip'},
{id: 3, is_guest: true},
{id: 4, is_guest: false},
]
// filter results
const filtered = data.filter(item => item.is_guest)
// just to show the result
document.querySelector('.debug').innerHTML = JSON.stringify(filtered, null, 2);
<pre><code class="debug"></code></pre>
I do get my data from mongoDB with
Content.find({}).fetch()
So there is an array with 1000 documents. Every document has a category field.
const data = [
{
title: 'Article 1',
category: 'cars'
},
{
title: 'Article 2',
category: 'vegetables'
}
]
Now I would like to display a list with the documents organized in categories (Getting an own list for each category).
I think the best way I should go is to create a map. But I do not know how to do that.
This is what I am doing:
First I've hardcoded the categories (knowing this is the worst thing I can do)
Then...
categories.map(category => {
return (<List data={data.filter(d => d.category === category)} />)
}
With this I will go through 30 categories and do a filter on my data 30 times. I think it would be smarter to create a kind of sorted dataset once at the beginning.
If you'd prefer using lodash, then you can sort your data set using sortBy
_.sortBy(data, ['category', 'title']);
data.map(doc => ( console.log(doc); ));
This will sort your data ascending first by category and then by title.
OR if you want to break your items into arrays based on category use groupBy:
_.groupBy(data, 'category');
_.each(data, itemsByCategory => {
return <List data={itemsByCategory} />;
});
Since aggregating data on mongodb is not an option, I would create a map on the client side by looping once over the data, like:
var titlesByCategory = {};
for (let value of data) {
if (titlesByCategory[data.category]) {
titlesByCategory[value.category].push(value.title);
}
else {
titlesByCategory[value.category] = [value.title];
}
}
You can then access the array of titles of a given a category just using the category as a key; for instance titlesByCategory[vegetables] will return the array of vegetables over which you can do loop/map or whatever you need.
using underscoreJS
_.groupBy(data, 'length');