apply multiple filters react - javascript

I have 2 buttons which when clicked should filter by novelty or offer , I am able to make it so that when novelty is clicked it will filter by this but I am unable to make it so that if both are click it will filter by both novelty and offer
How can I make it so that when both novelty and offer are clicked it will filter by both of these?
https://www.webpackbin.com/bins/-KpVGNEN7ZuKAFODxuER
import React from 'react'
export default class extends React.Component {
constructor() {
super()
this.state = {
products: [
{ id: 1, novelty: true, offer: false, name: 'test1' },
{ id: 2, novelty: true, offer: true, name: 'test2' },
{ id: 3, novelty: false, offer: true, name: 'test3' }
],
display: 'all',
filters: [
{novelty:'true'},
{offer: 'true'}
]
}
}
setCategory (category) {
this.setState({
display: category
});
}
render() {
return(
<div>
<button onClick={()=>this.setCategory(true)}>Akce</button>
<button onClick={()=>this.setCategory(true)}>Offer</button>
{
this.state.products.filter( product =>
products.offer === this.state.display ||
this.state.display==='all')
.map(product =>
<div>{product.name}</div>
)
}
</div>
)
}
}

Here is the final version I've come up with:
import React from 'react'
export default class extends React.Component {
constructor() {
super()
this.state = {
products: [
{ id: 1, novelty: true, offer: false, name: 'test1' },
{ id: 2, novelty: true, offer: true, name: 'test2' },
{ id: 3, novelty: false, offer: true, name: 'test3' }
],
filters: {
novelty: true,
offer: true
}
}
}
setCategory (category) {
this.setState((state) => ({
filters: Object.assign({}, state.filters, { [category]: !state.filters[category] })
}));
}
render() {
console.log(this.state.filters)
return(
<div>
<button onClick={()=>this.setCategory('novelty')}>Akce</button>
<button onClick={()=>this.setCategory('offer')}>Offer</button>
{ this.state.products
.filter(product => product.novelty === this.state.filters.novelty || product.offer === this.state.filters.offer)
.map(product =>
<div key={product.id}>{product.name}</div>
)}
</div>
)
}
}
https://www.webpackbin.com/bins/-KpVHqfkjeraq6pGvHij
A few things:
Using a boolean instead of a string in your case is more adapted. (true instead of 'true').
display: 'all' isn't required for your use case. You can compute this value from your filters if you need to.
setCategory receive which category you want to set as a param.
I would rename setCategory to setFilter
Also, I'm using the asycnhronous version of setState. This allows you to hand in a function.
this.setState((state) => ({
filters: Object.assign({}, state.filters, { [category]: !state.filters[category] })
}));
Here I'm using Object.assign to create a new Object. I populate him with state.filters and finally I update the filter you want to.
category will either be novelty or offer and thanks to that I'm using the shorthand version of [category].
To conclude, I also update your filter function to check the product.novelty against the filter.novelty or the product.offer with the filter.offer

Related

Filter common elements of two arrays with useEffect?

I have these two states that consist in two arrays.
const bundle = [
{
id: 1,
type: "schedule",
action: "skip",
target_action: "reset"
},
{
id: 2,
type: "schedule",
action: "reset",
target_action: "skip"
},
{
id: 1,
type: "check",
action: "reset",
target_action: "skip"
},
{
id: 2,
type: "check",
action: "skip",
target_action: "reset"
}
];
const active = [
{
id: 1,
type: "schedule",
isActive: true
},
{
id: 2,
type: "schedule",
isActive: false
},
{
id: 1,
type: "check",
isActive: true
},
{
id: 2,
type: "check",
isActive: false
}
];
When items in active turns inactive (isActive: false) by clicking a button, they get filtered out of the array.
const handleActive = (item) => {
setActive((prevState) => {
const existingItem = prevState.find(
(activeItem) =>
activeItem.id === bundleItem.id &&
activeItem.type === bundleItem.type,
);
if (!existingItem) {
return [...active, { ...bundleItem, isActive: true }];
}
return prevState
.map((oldItem) => {
return oldItem.id === existingItem.id &&
oldItem.type === bundleItem.type
? { ...existingItem, isActive: !oldItem.isActive }
: oldItem;
})
.filter((itemToFilter) => itemToFilter.isActive);
});
};
Basically, I want to implement a useEffect that dynamically updates bundle in two ways simultaneously:
items must have at least one of action or c_action keys
when active gets updated (some elements get inactive and filtered out), I want to keep only the common items between the two arrays (same ID and type)
I implemented these two effects.
The first one to filter out the inactive elements from bundle:
React.useEffect(() => {
setBundle((prevState) => {
return bundle.filter((bundleItem) =>
active.some(
(activeItem) =>
activeItem.id === bundleItem.id &&
activeItem.type === bundleItem.type,
),
);
})
}, [active]);
The other one to filter out from bundle elements that doesn't "action" or "c_action" key.
React.useEffect(() => {
setBundle((prevState) => {
return bundle.filter(
(bundleItem) => bundleItem.action || bundleItem.c_action
);
});
}, [bundle]);
The second useEffect I implemented throws an infinite loop: bundle gets endlessly updated.
Thanks, a lot.
It seems to me that what you're looking for is actually an useMemo use case
you can do something like
const filteredBundle = useMemo(()=> bundle.filter(
(bundleItem) => bundleItem.action || bundleItem.c_action
),[bundle]);
And use the filtered bundle where makes sense

Fast access to json tree data structure

I have a reducer which holds tree data structure (more then 100_000 items total). This is what the data looks like
[
{
text: 'A',
expanded: false,
items:
[
{
text: 'AA',
expanded: false
},
{
text: 'AB',
expanded: false,
items:
[
{
text: 'ABA',
expanded: false,
},
{
text: 'ABB',
expanded: false,
}
]
}
]
},
{
text: 'B',
expanded: false,
items:
[
{
text: 'BA',
expanded: false
},
{
text: 'BB',
expanded: false
}
]
}
]
What I need to do is access this items really fast using text as an id (need to toggle expanded each time user clicks on item in a treeview). Should I just copy whole structure in to dictionary or is there a better way?
Maybe the following will help, let me know if you need more help but please create a runnable example (code snippet) that shows the problem:
const items = [
{
text: 'A',
expanded: false,
items: [
{
text: 'AA',
expanded: false,
},
{
text: 'AB',
expanded: false,
items: [
{
text: 'ABA',
expanded: false,
},
{
text: 'ABB',
expanded: false,
},
],
},
],
},
{
text: 'B',
expanded: false,
items: [
{
text: 'BA',
expanded: false,
},
{
text: 'BB',
expanded: false,
},
],
},
];
//in your reducer
const mapItems = new Map();
const createMap = (items) => {
const recur = (path) => (item, index) => {
const currentPath = path.concat(index);
mapItems.set(item.text, currentPath);
//no sub items not found in this path
if (!item.items) {
return;
}
//recursively set map
item.items.forEach(recur(currentPath));
};
//clear the map
mapItems.clear();
//re create the map
items.forEach(recur([]));
};
const recursiveUpdate = (path, items, update) => {
const recur = ([current, ...path]) => (item, index) => {
if (index === current && !path.length) {
//no more subitems to change
return { ...item, ...update };
}
if (index === current) {
//need to change an item in item.items
return {
...item,
items: item.items.map(recur(path)),
};
}
//nothing to do for this item
return item;
};
return items.map(recur(path));
};
const reducer = (state, action) => {
//if you set the data then create the map, this can make
// testing difficult since SET_ITEM works only when
// when you call SET_DATA first. You should not have
// side effects in your reducer (like creating the map)
// I broke this rule in favor of optimization
if (action.type === 'SET_DATA') {
createMap(action.payload); //create the map
return { ...state, items };
}
if (action.type === 'SET_ITEM') {
return {
...state,
items: recursiveUpdate(
mapItems.get(action.payload.text),
state.items,
action.payload
),
};
}
return state;
};
//crate a state
const state = reducer(
{},
{ type: 'SET_DATA', payload: items }
);
const changed1 = reducer(state, {
type: 'SET_ITEM',
payload: { text: 'A', changed: 'A' },
});
const {
items: gone,
...withoutSubItems
} = changed1.items[0];
console.log('1', withoutSubItems);
const changed2 = reducer(state, {
type: 'SET_ITEM',
payload: { text: 'ABB', changed: 'ABB' },
});
console.log('2', changed2.items[0].items[1].items[1]);
const changed3 = reducer(state, {
type: 'SET_ITEM',
payload: { text: 'BA', changed: 'BA' },
});
console.log('3', changed3.items[1].items[0]);
If all you wanted to do is toggle expanded then you should probably do that with local state and forget about storing expanded in redux unless you want to expand something outside of the component that renders the item because expanded is then shared between multiple components.
I think you may mean that the cost of handling a change of expansion is really high (because potentially you close/open a node with 100000 leaves and then 100000 UI items are notified).
However, this worries me as I hope only the expanded UI items exist at all (e.g. you don't have hidden React elements for everything, each sitting there and monitoring a Redux selector in case its part of the tree becomes visible).
So long as elements are non-existent when not expanded, then why is expansion a status known by anything but its immediate parent, and only the parent if it's also on screen?
I suggest that expansion state should be e.g. React state not Redux state at all. If they are on screen then they are expanded, optionally with their children expanded (with this held as state within the parent UI element) and if they are not on screen they don't exist.
Copy all the individual items into a Map<id, Node> to then access it by the ID.
const data = []// your data
// Build Map index
const itemsMap = new Map();
let itemsQueue = [...data];
let cursor = itemsQueue.pop();
while (cursor) {
itemsMap.set(cursor.text, cursor);
if (cursor.items)
for (let item of cursor.items) {
itemsQueue.push(item);
}
cursor = itemsQueue.pop();
}
// Retrieve by text id
console.log(map.get('ABB'));
// {
// text: 'ABB',
// expanded: false,
// }

How to check whether a v-checkbox is selected in Vue JS?

I have a checkbox with the following events and props:
<v-checkbox
v-for="planets in allPlanets" :key="`planets_${planets.id}`"
:label="planets.name"
:value="planets.id"
v-model="selectedPlanets"
/>
Given that all of the checkboxes are brought in using a v-for, how can I check whether a checkbox is selected using a method or mounted function in Vue JS?
For example:
methods: {
checkSelected() {
????
},
Add a planets.selected key.
allPlanets: [
{ name: 'Planet name', value: 'Planet value' , selected : false },
...,
...
],
}
And in your template:
<v-checkbox
v-for="planets in allPlanets" :key="`planets_${planets.id}`"
:label="planets.name"
:value="planets.id"
v-model="planets.selected"
/>
Similar to:
Display multiple checkbox in table format using <v-for> and <v-checkbox> in Vuetify?
you have to make such a structure, so you know for each id, whether it is checked or not
new Vue({
data: () => ({
allPlanets: [
{
id: 32,
name: "planent",
selected: false
},
{
id: 365,
name: "planet 2",
selected: false
}
],
}),
methods: {
checkSelectedByIndex(index) {
return this.allPlanets[index].selected
},
checkSelectedById(id) {
return this.allPlanets.find(p => p.id === id)?.selected ?? false
}
}
});
and in the you have to set the v-model="planets.selected"
Given your layout the simplest methods is:
methods: {
checkSelected(id) {
return this.selectedPlanets.includes(id)
},
}

Angular subscription is not working as I expect

So I have a shop page with a child component called FilterBarComponent and onInit I want it to emit all the category as by default I want all the products in the shop to be rendered, but on my homePageComponent I have a button that allows a user to navigate to the shopPage and view a specific category for e.g a button that says "view shirts". My problem is that the default categories array occurs after the subscriber function finishes and also in the subscriber the event emitter does not fire.
Here is another question of mine that relates to this problem.
Angular EventEmitter is not emitting in subscriber
FilterBarComponent
categories = [];
#Output() filteredCategory = new EventEmitter<any>();
#Output() maxiumPriceEmitter = new EventEmitter<any>();
categorySub: Subscription;
formatLabel(value: number) {
return 'R' + value;
}
constructor(private shopService: ShopService) {}
ngOnInit() {
this.initCategories();
this.filterCategories();
this.updateCategories();
}
filterCategories() {
this.shopService.filterCategories.subscribe(
(fCategory: string) => {
this.categories.map(category => {
category.checked = category.name === fCategory;
});
this.updateCategories();
});
}
initCategories() {
this.categories = [
{ name: 'dress', checked: true, displayName: 'Dresses' },
{ name: 'top', checked: true, displayName: 'Shirts' },
{ name: 'skirt', checked: true, displayName: 'Skirts/Pants' },
{ name: 'purse', checked: true, displayName: 'Purse' },
{ name: 'bag', checked: true, displayName: 'Bags' },
];
}
updateCategories() {
const categories = this.categories
.filter((category) => {
return category.checked;
});
console.log(categories);
this.filteredCategory.emit(categories);
}
in the console at first I get the correct result
but then categories array resets
[{}]
{name: "top", checked: true, displayName: "Shirts"}
[{…}, {…}, {…}, {…}, {…}]
{name: "dress", checked: true, displayName: "Dresses"}
1: {name: "top", checked: true, displayName: "Shirts"}
2: {name: "skirt", checked: true, displayName: "Skirts/Pants"}
3: {name: "purse", checked: true, displayName: "Purse"}
4: {name: "bag", checked: true, displayName: "Bags"}
length: 5
the Observable in ShopService
filterCategories = new BehaviorSubject("category");
I owe this answer to #Józef Podlecki for another question that he answered of mine.
I need to use a BehaviorSubject instead of a regular subject in the ShopService
filterCategories = new BehaviorSubject("all");
Filter Bar Component
ngOnInit() {
this.initCategories();
this.filterCategories();
}
filterCategories() {
this.shopService.filterCategories.subscribe((fCategory: string) => {
if (fCategory === 'all') {
this.updateCategories();
} else {
this.categories.map((category) => {
category.checked = category.name === fCategory;
});
this.updateCategories();
}
});
}

How to propagate state to the root of nested tree from a deep node without using Flux?

I am rendering a nested comment tree, I am not able to figure how to update the tree data present in the comment container from one of the deeply nested comments. I have created a basic fiddle representing the scenario, check it out HERE. The button in each node is supposed to invert the value of "likes" property, which should be coming from the state in parent container. Looking for a non redux solution.
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {
tree: [
{
id: "cake",
likes: true,
kids: [
{
id: "banana",
likes: false,
kids: []
},
{
id: "rocket",
likes: false,
kids: [
{
id: "homework",
likes: true,
kids: [
{
id: "something",
likes: true,
kids: []
}
]
}
]
}
]
}
]
};
}
render() {
return (
<div>
{this.state.tree.map(value => <Kid value={value} />)}
</div>
);
}
}
class Kid extends React.Component {
render() {
return (
<div style={{margin: '20px', border: '5px dashed #DCEDC8'}}>
<span>id: {this.props.value.id}</span><br />
<span>likes: {this.props.value.likes ? 'yes' : 'no'}</span><br />
<button>invert</button>
{this.props.value.kids.length
? this.props.value.kids.map(value => <Kid value={value} />)
: null}
</div>
);
}
}
React.render(<Parent />, document.getElementById("container"));
<script src="https://facebook.github.io/react/js/jsfiddle-integration.js"></script>
<div id="container">
<!-- This element's contents will be replaced with your component. -->
</div>
The recommendation for Redux state is to keep it normalized, something like this:
this.state = {
normalized: [
cake: {likes: true, kids: ["banana", "rocket"]}
banana: {likes: false, kids: []},
rocket: {likes: false, kids: ["homework"]},
homework: {likes: true, kids: ["something"]},
something: {likes: true, kids: []},
],
root: "cake",
};
Then, if you have an id, you would refer to an item with this.state.normalized[id]
For example, to traverse the tree and apply a function to every node, you would do:
function traverse(node, func) {
func(node);
for (let i = 0, len = node.kids.length; i < len; i++) {
traverse(this.state.normalized[node.kids[i]]);
}
}
traverse(this.state.normalized[this.state.root]);
Normalizr can be useful to normalize nested API responses if you don't want to code your own solution.
If you really want to keep an immutable tree in your state, Immutable.js is good at letting your performantly alter a tree in a single line without mutating it.
Here is the final code that does what you wanted.
There are a lot of things here that you need to pay attention to be able to understand how it works.
While rendering each of the kid, the parent path and kid's id becomes a kid's path. When the button is clicked this path gets passed to the Parent component where the state gets manipulated using this path.
This assumes that id will be unique for siblings.
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {
tree: [
{
id: "cake",
likes: true,
kids: [
{
id: "banana",
likes: false,
kids: []
},
{
id: "rocket",
likes: false,
kids: [
{
id: "homework",
likes: true,
kids: [
{
id: "something",
likes: true,
kids: []
}
]
}
]
}
]
}
]
};
this.onInvertHandler = this.onInvertHandler.bind(this);
}
onInvertHandler(path) {
this.setState((prevState) => {
const pathKeys = path.split(':');
let obj = prevState['tree'];
for(let i=0;i<pathKeys.length;i++) {
obj = i >0 ? obj.kids : obj;
obj = obj.find((el)=>{ return el.id == pathKeys[i] });
}
obj['likes'] = !obj['likes'];
return prevState;
})
};
render() {
return (
<div>
{this.state.tree.map(value => <Kid value={value} path = {value.id} onInvertHandler={this.onInvertHandler}/>)}
</div>
);
}
}
class Kid extends React.Component {
constructor(props){
super(props);
this.onInvertHandler = this.onInvertHandler.bind(this);
}
onInvertHandler(p) {
let params = typeof p === 'string' ? p : this.props.path;
this.props.onInvertHandler(params);
};
render() {
return (
<div style={{margin: '20px', border: '5px dashed #DCEDC8'}}>
<span>id: {this.props.value.id}</span><br />
<span>likes: {this.props.value.likes ? 'yes' : 'no'}</span><br />
<button onClick = {this.onInvertHandler}>invert</button>
{this.props.value.kids.length
? this.props.value.kids.map(value => <Kid value={value} path = {this.props.path + ':'+value.id} onInvertHandler={this.onInvertHandler}/>)
: null}
</div>
);
}
}
React.render(<Parent />, document.getElementById("container"));

Categories

Resources