Filter array using multiple keys - javascript

I'm trying to render grid with multiple toggling filters. I'm Using react hooks.
This is data array:
const items = [
{
name: 'hotel1',
type: 'hotel'
},
{
name: 'bar1',
type: 'bar'
},{
name: 'entertainment1',
type: 'entertainment'
},{
name: 'equipment1',
type: 'equipment'
},
{
name: 'shop1',
type: 'shop'
}
]
const initialFilters = [
{
id: 1,
active: false,
type: 'bar'
},
{
id: 2,
active: false,
type: 'shop'
},
{
id: 3,
active: false,
type: 'hotel'
},
{
id: 4,
active: false,
type: 'entertainment'
},
{
id: 5,
active: false,
type: 'equipment'
},
];
const [data, setData] = useState(items);
const [filterItems, setFilteredItems] = useState(initialFilters);
currently I'm filtering with single key that is passed
const mainFilter = (key) => {
setData(items.filter(x => x.type === key));
}
and filter buttons with grid item names are rendered:
return (
<div>
<ul>
{filterItems.map(x =>
<li key={x.type}><a
className={x.active == true ? 'active' : ''}
onClick={() => mainFilter(x.type)}>
{x.type}
</a></li>
)}
</ul>
<div>{data.map(item => <div>{item.name}</div>}</div>
</div>
)
I need to get the functionality where when pressed for example shop filter, it should only leave items with type shop. if you press bar filter, it should only leave items with bar an shop and it should work all the way to all 5 filters.
if none are selected, it should show full array.
I've tried converting logic from this:
https://gist.github.com/jherax/f11d669ba286f21b7a2dcff69621eb72#file-filterplainarray-js
but no luck yet

You can use the function every for checking if all flags have value false
This code snippet, have an initialFilter with all the flags equal to false
const items = [{name: 'hotel1',type: 'hotel'},{name: 'bar1',type: 'bar'}, {name: 'entertainment1',type: 'entertainment'}, {name: 'equipment1',type: 'equipment'},{name: 'shop1',type: 'shop'}];
const initialFilters = [{id: 1,active: false,type: 'bar'},{id: 2,active: false,type: 'shop'},{id: 3,active: false,type: 'hotel'},{id: 4,active: false,type: 'entertainment'},{id: 5,active: false,type: 'equipment'}];
let allFalse = initialFilters.every(({active}) => !active);
if (allFalse) console.log(items);
Otherwise, you can use the function filter along with the function some:
This code snippet has as active shop and entertainment
const items = [{name: 'hotel1',type: 'hotel'},{name: 'bar1',type: 'bar'}, {name: 'entertainment1',type: 'entertainment'}, {name: 'equipment1',type: 'equipment'},{name: 'shop1',type: 'shop'}];
const initialFilters = [{id: 1,active: false,type: 'bar'},{id: 2,active: true,type: 'shop'},{id: 3,active: false,type: 'hotel'},{id: 4,active: true,type: 'entertainment'},{id: 5,active: false,type: 'equipment'}];
let fltered = items.filter(({type}) => initialFilters.some(({type: t, active}) => t === type && active));
console.log(fltered);

You can store the selected keys and then sort by checking if they are included:
const mainFilter = (keys) => {
setData(items.filter(x => keys.includes(x.type)));
}
This implementation would need a change in your onClick function - instead of calling a function with the key you'll need to store the keys.

Related

Node Js how to fetch data from database in an hierarchical way

I'm writing a back code using NodeJs to fetch some data from backend, I want dataBase data to be like this
like this:
data = [{
name: "Admin",
id: '1',
children: [
{ name: "Admin", id: "1" },
{ name: "groupe1", id: "2" },
{
name: "groupe2", id: "1455", children: [
{ name: "groupe2", id: "1455" },
{ name: "gro", id: "5444" },
{ name: "hhrr", id: "45" }
]
}
]
}]
the idea is simple we have a list of group each group has a parent I want to display all the groups list in an hierarchical way the top one of the tree is done
Some groups are parents and groups in the same time and some others are only groups if the group is not parent we add an object with its name and ID in the array of children of his parent
if this groups is a parent that's mean it has children we add an object with its ID and name in the array of children of his parents, and we add property children for the object which is array named children with for the first time an object with the name and the id of the group etc...
i tryed to do this but it did not work
const getParentsByType = async ({ name, _id }) => {
let parentResult = [
{
id: _id,
name: name,
children: [
{
id: _id,
name: name,
},
],
},
];
parentResult= await findParent(_id, parentResult[0].children, 0);
return parentResult;
};
const findParent = async (parentId, parentResult, itemPos) => {
let children = await Models.GroupModel.find({ parent: parentId, status: true }).select('name _id');
for (let i = 0; i < children.length; i++) {
let childrenList = await Models.GroupModel.find({ parent: children[i]._id, status: true }).select('name _id');
if (childrenList.length != 0) {
parentResult.push(buildParentWithChild(children[i]._id, children[i].name));
findParent(children[i]._id,parentResult.children[i],itemPos++)
} else {
parentResult.push(buildParent(children[i]._id, children[i].name));
}
}
return parentResult
};
and this the model of the data base
const Group = mongoose.Schema({
name: {
type: String,
required: true,
},
status: {
type: Boolean,
required: true,
},
parent: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Group',
},
});
i had two days trying to resolve tis but with no result
i need some helps and Thank you
Try parsing your returned data. It validates your data as objects i dont see any problem with your function regardless i still have no idea what format your a trying to build.
let children = JSON.parse(JSON.stringify(await Models.GroupModel.find({ parent: parentId, status: true }).select('name _id')));
let childrenList = JSON.parse(JSON.stringify(await Models.GroupModel.find({ parent: children[i]._id, status: true }).select('name _id')));
If I understand you right, you want to convert the array returned by Models.GroupModel.find, and which looks like
var dbresult = [
{_id: "1", parent: null, name: "one"},
{_id: "2", parent: "1", name: "two"}
];
into a hierarchical structure. This can be done with a function that adds all children of a given parent p, including, recursively, their children. Like the following:
function children(p) {
var result = [];
for (r of dbresult) if (r.parent === p) {
var row = {_id: r._id, name: r.name};
var chld = children(r._id);
if (chld.length > 0) row.children = chld;
result.push(row);
}
return result;
}
console.log(JSON.stringify(children(null)));
Note that this approach requires only one database access (to fill the dbresult) and is therefore probably faster than your findParent function.

JavaScript Get Indexes based from the Selected Array of Object Id

Today, I'm trying to get the list of javascript index based from the selected data id that I have.
I'm following this guide from https://buefy.org/documentation/table/#checkable where it needs something like this: checkedRows: [data[1], data[3]] to able to check the specific row in the table.
What I need to do is to check the table based from my web API response.
I have this sample response data.
response.data.checkedRows // value is [{id: 1234}, {id: 83412}]
and I have the sample data from the table.
const data = [{
id: 1234,
name: 'Jojo'
},{
id: 43221,
name: 'Jeff'
},{
id: 83412,
name: 'Kacey'
}]
So basically, I need to have something, dynamically, like this: checkedRows: [data[0], data[2]] because it matches the data from the response.data.checkedRows
So far, I tried using forEach
let selectedIndex = [];
response.data.checkedRows.forEach((d) => {
this.data.forEach((e) => {
if (d.id=== e.id) {
// need the result to be dynamic depending on the response.data.checkedRows
this.checkedRows = [data[0], data[2]]
}
});
});
I'm stuck here because I'm not sure how can I get the index that matches the selected checkedRows from response.
Any help?
Map the response checkedRows, and in the callback, .find the matching object in the array:
const checkedRows = [{id: 1234}, {id: 83412}];
const data = [{
id: 1234,
name: 'Jojo'
},{
id: 43221,
name: 'Jeff'
},{
id: 83412,
name: 'Kacey'
}];
const objs = checkedRows.map(({ id }) => (
data.find(obj => obj.id === id)
));
console.log(objs);
If there are a lot of elements, you can use a Set of the IDs to find instead to decrease the computational complexity:
const checkedRows = [{id: 1234}, {id: 83412}];
const data = [{
id: 1234,
name: 'Jojo'
},{
id: 43221,
name: 'Jeff'
},{
id: 83412,
name: 'Kacey'
}];
const ids = new Set(checkedRows.map(({ id }) => id));
const objs = data.filter(obj => ids.has(obj.id));
console.log(objs);

How to get value of their object in react hooks array?

Good afternoon, I rarely write here. But now I really can't understand.
I am using React Select to display select. In the onChange attribute, I pass a function that forms the object and writes it to UseStat. But then I try to find an object using the find and
take an array of values from it.
const [selectedSpecificationValues, setSelectedSpecificationValues] = useState([])
const setSelectedSpecificationValuesHandler = (e, s) => {
const maybeSelectedSpecification = selectedSpecificationValues.find(
ss => ss._id === s._id
)
const objForWrite = {
_id: s._id,
name: s.name,
values: e,
}
if (maybeSelectedSpecification) {
const index = selectedSpecificationValues.indexOf(
maybeSelectedSpecification
)
let newArr = [...selectedSpecificationValues]
newArr[index] = objForWrite
setSelectedSpecificationValues(newArr)
} else {
setSelectedSpecificationValues([
...selectedSpecificationValues,
objForWrite,
])
}
}
const ssTestVal = Id => {
let result = []
if (selectedSpecificationValues.length > 0) {
const foundItem = selectedSpecificationValues.find(i => i._id === Id)
console.log(Id, foundItem)
if (foundItem) {
result = foundItem.values
}
}
return result
}
/* specifications = [
{
values: [
{
value: 0,
label: '480 min',
},
{
value: 1,
label: '120 min',
},
],
_id: '5fe74eae07905e53ebf263ec',
name: 'Duration',
slug: 'duration',
createdAt: '2020-12-26T14:54:38.362Z',
updatedAt: '2020-12-29T08:37:18.962Z',
__v: 1,
},
{
values: [
{
value: 0,
label: 'Photobook',
},
{
value: 1,
label: 'Photocard',
},
{
value: 2,
label: 'Album',
},
{
value: 3,
label: 'DVD',
},
{
value: 4,
label: 'Stickers',
},
{
value: 5,
label: 'CD',
},
],
_id: '5fe74e9107905e53ebf263eb',
name: 'Includes',
slug: 'includes',
createdAt: '2020-12-26T14:54:09.267Z',
updatedAt: '2020-12-26T16:10:16.283Z',
__v: 9,
},
] */
{
specifications &&
specifications.map((s, idx) => (
<Select
classNamePrefix='select2-selection'
options={s.values}
value={() => ssTestVal(s._id)}
onChange={e => setSelectedSpecificationValuesHandler(e, s)}
isMulti
/>
))
}
It is also important to understand that I loop a lot of selections in order to select different characteristics and their values.
I will be glad to help!
https://codesandbox.io/s/serverless-night-kez18?file=/src/App.js
Looks like minor issue with how you were computing the value for the sub-select inputs. You were defining it as though it were a callback.
<Select
classNamePrefix="select2-selection"
options={s.values}
value={() => ssTestVal(s._id)} // <-- not correct, not a callabck
onChange={(e) => setSelectedSpecificationValuesHandler(e, s)}
isMulti
/>
It should just be immediately invoked to compute and return an input's value.
<Select
classNamePrefix="select2-selection"
options={s.values}
value={ssTestVal(s._id)} // <-- invoke immediately for return value
onChange={(e) => setSelectedSpecificationValuesHandler(e, s)}
isMulti
/>

JavaScript, looping, and functional approach

Data Structure coming back from the server
[
{
id: 1,
type: "Pickup",
items: [
{
id: 1,
description: "Item 1"
}
]
},
{
id: 2,
type: "Drop",
items: [
{
id: 0,
description: "Item 0"
}
]
},
{
id: 3,
type: "Drop",
items: [
{
id: 1,
description: "Item 1"
},
{
id: 2,
description: "Item 2"
}
]
},
{
id: 0,
type: "Pickup",
items: [
{
id: 0,
description: "Item 0"
},
{
id: 2,
description: "Item 2"
}
]
}
];
Each element represents an event.
Each event is only a pickup or drop.
Each event can have one or more items.
Initial State
On initial load, loop over the response coming from the server and add an extra property called isSelected to each event, each item, and set it as false as default. -- Done.
This isSelected property is for UI purpose only and tells user(s) which event(s) and/or item(s) has/have been selected.
// shove the response coming from the server here and add extra property called isSelected and set it to default value (false)
const initialState = {
events: []
}
moveEvent method:
const moveEvent = ({ events }, selectedEventId) => {
// de-dupe selected items
const selectedItemIds = {};
// grab and find the selected event by id
let foundSelectedEvent = events.find(event => event.id === selectedEventId);
// update the found event and all its items' isSelected property to true
foundSelectedEvent = {
...foundSelectedEvent,
isSelected: true,
items: foundSelectedEvent.items.map(item => {
item = { ...item, isSelected: true };
// Keep track of the selected items to update the other events.
selectedItemIds[item.id] = item.id;
return item;
})
};
events = events.map(event => {
// update events array to have the found selected event
if(event.id === foundSelectedEvent.id) {
return foundSelectedEvent;
}
// Loop over the rest of the non selected events
event.items = event.items.map(item => {
// if the same item exists in the selected event's items, then set item's isSelected to true.
const foundItem = selectedItemIds[item.id];
// foundItem is the id of an item, so 0 is valid
if(foundItem >= 0) {
return { ...item, isSelected: true };
}
return item;
});
const itemCount = event.items.length;
const selectedItemCount = event.items.filter(item => item.isSelected).length;
// If all items in the event are set to isSelected true, then mark the event to isSelected true as well.
if(itemCount === selectedItemCount) {
event = { ...event, isSelected: true };
}
return event;
});
return { events }
}
Personally, I don't like the way I've implemented the moveEvent method, and it seems like an imperative approach even though I'm using find, filter, and map.
All this moveEvent method is doing is flipping the isSelected flag.
Is there a better solution?
Is there a way to reduce the amount of looping? Maybe events should be an object and even its items. At least, the lookup would be fast for finding an event, and I don't have to use Array.find initially. However, I still have to either loop over each other non selected events' properties or convert them back and forth using Object.entries and/or Object.values.
Is there more a functional approach? Can recursion resolve this?
Usage and Result
// found the event with id 0
const newState = moveEvent(initialState, 0);
// Expected results
[
{
id: 1,
type: 'Pickup',
isSelected: false,
items: [ { id: 1, isSelected: false, description: 'Item 1' } ]
}
{
id: 2,
type: 'Drop',
// becasue all items' isSelected properties are set to true (even though it is just one), then set this event's isSelected to true
isSelected: true,
// set this to true because event id 0 has the same item (id 1)
items: [ { id: 0, isSelected: true, description: 'Item 0' } ]
}
{
id: 3,
type: 'Drop',
// since all items' isSelected properties are not set to true, then this should remain false.
isSelected: false,
items: [
{ id: 1, isSelected: false, description: 'Item 1' },
// set this to true because event id 0 has the same item (id 2)
{ id: 2, isSelected: true, description: 'Item 2' }
]
}
{
id: 0,
type: 'Pickup',
// set isSelected to true because the selected event id is 0
isSelected: true,
items: [
// since this belongs to the selected event id of 0, then set all items' isSelected to true
{ id: 0, isSelected: true, description: 'Item 0' },
{ id: 2, isSelected: true, description: 'Item 2' }
]
}
]
One of the problems with the current solution is data duplication. You are basically trying to keep the data between the different items in sync. Instead of changing all items with the same id, make sure there are no duplicate items by using an approach closer to what you would find in a rational database.
Let's first normalize the data:
const response = [...]; // data returned by the server
let data = { eventIds: [], events: {}, items: {} };
for (const {id, items, ...event} of response) {
data.eventIds.push(id);
data.events[id] = event;
event.items = [];
for (const {id, ...item} of items) {
event.items.push(id);
data.items[id] = item;
}
}
This should result in:
const data {
eventIds: [1, 2, 3, 0], // original order
events: {
0: { type: "Pickup", items: [0, 2] },
1: { type: "Pickup", items: [1] },
2: { type: "Drop", items: [0] },
3: { type: "Drop", items: [1, 2] },
},
items: {
0: { description: "Item 0" },
1: { description: "Item 1" },
2: { description: "Item 2" },
},
};
The next thing to realize is that the isSelected property of an event is computed based on the isSelected property of its items. Storing this would mean more data duplication. Instead calculate it though a function.
const response = [{id:1,type:"Pickup",items:[{id:1,description:"Item 1"}]},{id:2,type:"Drop",items:[{id:0,description:"Item 0"}]},{id:3,type:"Drop",items:[{id:1,description:"Item 1"},{id:2,description:"Item 2"}]},{id:0,type:"Pickup",items:[{id:0,description:"Item 0"},{id:2,description:"Item 2"}]}];
// normalize incoming data
let data = { eventIds: [], events: {}, items: {} };
for (const {id, items, ...event} of response) {
data.eventIds.push(id);
data.events[id] = event;
event.items = [];
for (const {id, ...item} of items) {
event.items.push(id);
data.items[id] = item;
item.isSelected = false;
}
}
// don't copy isSelected into the event, calculate it with a function
const isEventSelected = ({events, items}, eventId) => {
return events[eventId].items.every(id => items[id].isSelected);
};
// log initial data
console.log(data);
for (const id of data.eventIds) {
console.log(`event ${id} selected?`, isEventSelected(data, id));
}
// moveEvent implementation with the normalized structure
const moveEvent = (data, eventId) => {
let { events, items } = data;
for (const id of events[eventId].items) {
items = {...items, [id]: {...items[id], isSelected: true}};
}
return { ...data, items };
};
data = moveEvent(data, 0);
// log after data applying `moveEvent(data, 0)`
console.log(data);
for (const id of data.eventIds) {
console.log(`event ${id} selected? `, isEventSelected(data, id));
}
// optional: convert structure back (if you still need it)
const convert = (data) => {
const { eventIds, events, items } = data;
return eventIds.map(id => ({
id,
...events[id],
isSelected: isEventSelected(data, id),
items: events[id].items.map(id => ({id, ...items[id]}))
}));
};
console.log(convert(data));
Check browser console, for better ouput readability.
I'm not sure if this answers solves your entire problem, but I hope you got something useful info out of it.

Filter an array of objects with a second array with multiple values

I am trying to write a function to take the first object in the "parent" array, pull out the child field (which is in that array) and use that field to filter the second object called "child".
I want to get all the related records from the child object that are in the child field in the parent object.
Expected output
child: [
{
**id: 1,**
name: 'Jimmy Yukka',
},
{
**id: 2,**
name: 'Up North',
}
INPUT
Parent: [
{
**id: 1,**
name: 'Melbourne Bands',
**child: [1, 2]**
}
I have the following data
Parent: [
{
**id: 1,**
name: 'Melbourne Bands',
**child: [1, 2]**
},
{
id: 2,
name: 'Sydney Bands',
child: [3]
}
],
child: [
{
**id: 1,**
name: 'Jimmy Yukka',
},
{
**id: 2,**
name: 'Up North',
},
{
id: 3,
url: 'jimmyyukka.com',
name: 'INXS',
CreatedByUserId: 1
}
],
The code of the function I have implemented so far:
currentChildrenIds(ParentId, parentData, childData) {
const singleParentRecord = parentData.filter(function(parent) {
return parent.id === ParentId;
});
const parentsChildIds = singleParentRecord[0].books;
const childRecords = childData.filter(function(child) {
return child.id === parentsChildIds
});
return childRecords
}
NOTES
This bit here is where it is wrong
const childRecords = childData.filter(function(child) {
return child.id === parentsChildIds
This bit here is also a bit rubbish (hardcoding the [0])but not I'm not sure how I should be coding it correctly
const parentsChildIds = singleParentRecord[0].books;
here,
const childRecords = childData.filter(function(child) {
return child.id === parentsChildIds
parentsChildIds is a reference to an array: you don't want to test if an id is === to a a reference,
You have to be explicit and check if the id is contained in the array:
const childRecords = childData.filter(function(child) {
return parentsChildIds.includes(child.id)
Regarding the singleParentRecord[0] that does feel weird,
since you know the method filter will always return an array of size 1 or 0,
you can use the method find instead of filter
Also in functionnal programming (array functions such as filter, map, find...)
I advice you to read a bit about the arrow function syntax because:
The syntex is more dense and it makes it easier for your brain to understand when several functions are chained
If you want to use variables which are defined outside of the function it will be available only inside of an arrow function
your code with an arrow function:
const childRecords = childData.filter((child) => {
return child.id === parentsChildIds
}
Try this:
const Parent = [
{
id: 1,
name: 'Melbourne Bands',
child: [1, 2]
},
{
id: 2,
name: 'Sydney Bands',
child: [3]
}
];
const children = [
{
id: 1,
name: 'Jimmy Yukka',
},
{
id: 2,
name: 'Up North',
},
{
id: 3,
url: 'jimmyyukka.com',
name: 'INXS',
CreatedByUserId: 1
}
];
// We create a new array with Array.map
const result = Parent.map(parent => ({
// Spread properties of the parent
...parent,
// Override the child property and filter the children array with the `includes` method
child: children.filter(child => parent.child.includes(child.id)),
}))
console.log(result);

Categories

Resources