Related
so i am trying to make a filter for my products. And for that i initialised a state in a component called Product that has all the filters. And then, based on the input of the checkboxes that are in a child component that is called Category, the filter state changes.
here is my code :
Product.jsx:
import { useState } from "react";
import Filter from "./Filter.jsx";
import styles from "../styles/products.module.css";
const products = {
prod1: {
id: 1,
name: "prod1",
category: "laptops",
price: "80",
description: { brand: "lenovo", processor: "intel core i7" },
},
prod2: {
id: 2,
name: "prod2",
category: "laptops",
price: "10",
description: { brand: "lenovo", processor: "intel core i7" },
},
prod3: {
id: 3,
name: "prod3",
category: "laptops",
price: "100",
description: { brand: "msi", processor: "intel core i5" },
},
prod4: {
id: 4,
name: "prod4",
category: "laptops",
price: "200",
description: { brand: "msi", processor: "intel core i3" },
},
prod5: {
id: 5,
name: "prod5",
category: "phones",
price: "50",
description: { brand: "samsung", ram: "8gb", storage: "64gb" },
},
prod6: {
id: 6,
name: "prod6",
category: "phones",
price: "50",
description: { brand: "infinix", ram: "4gb", storage: "128gb" },
},
prod7: {
id: 7,
name: "prod7",
category: "phones",
price: "50",
description: { brand: "oppo", ram: "8gb", storage: "256gb" },
},
prod8: {
id: 8,
name: "prod8",
category: "accessories",
price: "99",
description: { type: "keyboard" },
},
prod9: {
id: 9,
name: "prod9",
category: "accessories",
price: "75",
description: { type: "mouse" },
},
};
function Products() {
const filter = {
laptops: {
brand: [],
processor: [],
},
phones: {
brand: [],
ram: [],
storage: [],
},
accessories: {
type: [],
},
};
const [filterList, setFilterList] = useState(filter);
const [category, setCategory] = useState("all");
const [range, setRange] = useState(9999);
const getFilterState = () => {
return filterList;
};
const setFilterState = (list) => {
setFilterList(list);
};
return (
<div className={styles.productsContainer}>
<Filter
range={[range, setRange]}
category={[category, setCategory]}
getfilterList={getFilterState}
setFilterState={setFilterState}
/>
<table className={styles.productsListContainer}>
<tr className={styles.tableColumn}>
<th className={styles.tableRow}>id</th>
<th className={styles.tableRow}>Name</th>
<th className={styles.tableRow}>price</th>
</tr>
{Object.keys(products).map((key) => {
let flag = true;
const product = products[key];
const categ = product.category;
if (
(categ === category || category === "all") &&
product.price <= range
) {
Object.entries(filter[categ]).forEach(([key, value]) => {
if (
!(
value.length === 0 || value.includes(product.description[key])
)
) {
flag = false;
}
});
if (flag) {
return (
<tr className={styles.tableColumn} key={product.id}>
<td className={styles.tableRow}>{product.id}</td>
<td className={styles.tableRow}>{product.name}</td>
<td className={styles.tableRow}>{product.price}</td>
</tr>
);
}
return null;
}
return null;
})}
</table>
</div>
);
}
export default Products;
Filter.jsx:
import CategoryComponent from "./Category";
import styles from "../styles/filter.module.css";
function Filter({
filters,
dispatch,
category,
getfilterList: filterList,
setFilterState: setFilterList,
range: rangeState,
}) {
const [Category, setCategory] = category;
const [Range, setRange] = rangeState;
const handleCategoryChanged = (event, categ, key) => {
let list = filterList();
if (list[categ][key].includes(event.currentTarget.value)) {
list[categ][key] = list[categ][key].filter(
(val) => val !== event.currentTarget.value
);
} else {
list[categ][key].push(event.currentTarget.value);
}
setFilterList(list);
if (Category !== categ) {
setCategory(categ);
} else {
let flag = false;
Object.keys(filterList()[categ]).forEach((key) => {
const obj = filterList()[categ][key];
if (obj.length !== 0) flag = true;
});
if (!flag) {
setCategory("all");
}
}
};
const handleRangeChanged = (event) => {
setRange(event.currentTarget.value);
};
return (
<div className="filter-container">
<h1>{Category}</h1>
<CategoryComponent
filterList={filterList()}
setFilterList={setFilterList}
category={Category}
handleCategoryChanged={handleCategoryChanged}
categories={{
brand: ["lenovo", "msi", "asus"],
processor: ["intel core i3", "intel core i5", "intel core i7"],
}}
>
laptops
</CategoryComponent>
<CategoryComponent
filterList={filterList()}
setFilterList={setFilterList}
handleCategoryChanged={handleCategoryChanged}
category={Category}
categories={{
brand: ["samsung", "infinx", "oppo"],
ram: ["8gb", "4gb", "2gb"],
storage: ["64gb", "128gb", "256gb"],
}}
>
phones
</CategoryComponent>
<CategoryComponent
filterList={filterList()}
setFilterList={setFilterList}
category={Category}
handleCategoryChanged={handleCategoryChanged}
categories={{
type: ["keyboard", "mouse", "screen"],
}}
>
accessories
</CategoryComponent>
<div className={styles.priceRangeContainer}>
<input
type="range"
min="0"
max="9999"
className={styles.priceRange}
value={Range}
onChange={handleRangeChanged}
/>
<span>{Range}DT</span>
</div>
</div>
);
}
export default Filter;
Category.jsx
import React from "react";
import { useState, useEffect } from "react";
import styles from "../styles/category.module.css";
import arrow from "../img/angle-up-solid-svgrepo-com.svg";
export default function Category({
handleCategoryChanged,
children,
categories,
category,
filterList,
setFilterList,
}) {
const [hiddenClass, setHiddenClass] = useState(styles.hidden);
const handleClick = () => {
if (hiddenClass) setHiddenClass("");
else setHiddenClass(styles.hidden);
};
return (
<div>
<h5>{category}</h5>
<div onClick={handleClick} className={styles.categoryBox}>
{children} <img style={{ width: 15 }} src={arrow} alt="" />
</div>
<div className={styles.hiddenScroll + " " + hiddenClass}>
{Object.entries(categories).map(([key, value]) => {
return (
<div key={key}>
<h5>{key}:</h5>
<ul>
{value.map((val) => {
console.log(filterList);
return (
<li key={val}>
<input
type="checkbox"
name={val}
id={val}
value={val}
onChange={(e) =>
handleCategoryChanged(e, children, key)
}
/>
<label htmlFor={val}>{val}</label>
</li>
);
})}
</ul>
</div>
);
})}
</div>
</div>
);
}
The problem is when i run my program the state changes when i check an only one checkbox, but when i click on more checkboxes nothing changes.
EDIT:
my components hierarchy :
products.jsx
filter.jsx
Category.jsx
in products.jsx :
i have a state filterList that has all the filters i need.
const filter = {
laptops: {
brand: [],
processor: [],
},
phones: {
brand: [],
ram: [],
storage: [],
},
accessories: {
type: [],
},
};
const [filterList, setFilterList] = useState(filter);
in Filter.jsx:
i have state filterList that was passed as a prop from the Products component.
and a function that handles any checkbox change from it's child component.
const handleCategoryChanged = (event, categ, key) => {
let list = filterList();
//testing if the value is already in the filterList if true we remove it
if (list[categ][key].includes(event.currentTarget.value)) {
list[categ][key] = list[categ][key].filter(
(val) => val !== event.currentTarget.value
);
} else {
// else we add it to the list
list[categ][key].push(event.currentTarget.value);
}
setFilterList(list);
Category.jsx:
here i created all my checkboxes with the onChange event.
But the problem is when more than one checkbox gets changed, my filterList state does not get re rendered.
You missed the useState in your Filter.jsx
Replacing
const [Category, setCategory] = category;
const [Range, setRange] = rangeState;
With
const [Category, setCategory] = useState(category);
const [Range, setRange] = useState(rangeState);
A small working example could be found at code sandbox
⚠️ You did not provide the parent of Filter.jsx so in the example only the laptop brands are working
I have many div in cascade.
and I want to apply alternated colors to my golbal div ( green, yellow for example ). But i want that colors start from the begening of the global div not from the begening of the div that contains it..
I created this using recursivity.
This is what i have ( I displayed div borders to explain the design of the page i have. )
This is what i want
React code
<div>
{
intervenants.map(i => {
return (updateListDiv(i))
})
}
</div>
const updateListDiv = (intervenant) => {
if (intervenant.children && intervenant.children.length > 0) {
return (
intervenant.children.map((int, index) => {
let n = int.name + ' ( ' + int.profile.name + ' ) '
return (<div className="a b" key={Math.random()}>
<a> {int.name} </a>
( {int.profile.name} )
{updateListDiv(int)}
</div>)
})
)
} else {
}
}
css
.a {
margin-left: 30px;
}
.b {
line-height: 24pt;
border: solid 1px black;
}
Every item needs to know the previous color. So you can store the state globally, or (probably better) pass the state to your updateListDiv function.
Here I call the state isEven:
import React from 'react';
const intervenants = [
{ name: 'a', profile: { name: 'pa' }, children: [ { name: 'a.a', profile: { name: 'pa.a' }, children: null }, { name: 'a.b', profile: { name: 'pa.b' }, children: null }, ] },
{ name: 'b', profile: { name: 'pb' }, children: [ { name: 'b.a', profile: { name: 'pb.a' }, children: null }, { name: 'b.b', profile: { name: 'pb.b' }, children: null }, ] },
{ name: 'c', profile: { name: 'pc' }, children: [ { name: 'c.a', profile: { name: 'pc.a' }, children: null }, { name: 'c.b', profile: { name: 'pc.b' }, children: null }, ] },
];
const updateListDiv = (intervenant, isEven) => {
if (!intervenant.children || intervenant.children.length < 1) {
return;
}
return (
intervenant.children.map((int, index) => {
isEven = !isEven;
return (<div
key={ index }
style={{
marginLeft: '30px',
border: 'solid 1px',
backgroundColor: isEven ? '#ff0' : '#0f0',
}}
>
{ int.name }
{ updateListDiv(int, isEven) }
</div>);
})
);
}
export const ColorTree = ()=>{
return (<div>
{ updateListDiv({ children: intervenants }, false) }
</div>);
};
Alternatively, you can pass down an overall index, and check for modulo-2.
I like this approach more, because this is more flexible (it might come in handy to have a "total" index inside the childs, and it also becomes possible to e.g. use modulo-3 or something)
const updateListDiv = (intervenant, overAllIndex) => {
// ...
return (
intervenant.children.map((int, index) => {
overAllIndex++;
return (<div
style={{
backgroundColor: (overAllIndex % 2) ? '#ff0' : '#0f0',
}}
>
{ updateListDiv(int, overAllIndex) }
</div>);
})
);
}
I want to get the following output for the following data.
・3
・1
and sample data :
export const dummyData = [
{
id: "1",
name: "a",
sub: [
{
id: "1#1",
name: "b",
sub_sub: [
{ id: "1#1#1", name: "b-a" },
{ id: "1#1#2", name: "b-b" },
]
},
{
id: "1#2",
name: "c",
sub_sub: [
{ id: "1#2#1", name: "c-a" },
]
},
]
},
{
id: "2",
name: "d",
sub: [
{
id: "2#1",
name: "e",
sub_sub: [
{ id: "1#2#1", name: "e-a" },
]
}
]
},
]
I want to count how many elements of sub_sub are includes in object "a" and "d".
So, I made the following code.
<template>
<div>
<ul>
<li v-for="item in items" :key="item.i">{{rowSpanCalc(item.id)}}</li>
</ul>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'nuxt-property-decorator'
import { dummyData } from '~/store/dummy'
#Component({})
export default class extends Vue {
items: any = []
created() {
this.items = dummyData
}
rowSpanCalc(item: any) {
const count = item.sub.reduce(
(total: any, curr: any) => total + curr.sub_sub.length,
0
)
return count;
}
}
</script>
I ran my code and got an error in console like
item.sub.reduce is not a function
Could anyone please advise me how to fix this errors?
Methods in the template are used as events handler not for rendering, try to use that method inside a computed property then use that property for render your items :
#Component({})
export default class extends Vue {
items: any = []
created() {
this.items = dummyData
}
get customItems(){
return this.items.map(item=>({...item,count:this.rowSpanCalc(item.id)}))
}
rowSpanCalc(item: any) {
const count = item.sub.reduce(
(total: any, curr: any) => total + curr.sub_sub.length,
0
)
return count;
}
}
template :
...
<li v-for="item in customItems" :key="item.id">{{item.count}}</li>
...
I'm new to reactJs, I'm not sure where it went wrong.
I suppose there is something wrong with binding input. I suppose, cant change input because of value={detail.name}. However, even though I have deleted value={detail.name}, Name: {detail.name} still keeps the original value.
Could somebody give me a hint?
import React, { Component } from "react";
import "./App.css";
class App extends Component {
constructor(props) {
super(props);
this.state = {
details: [
{ id: 1, name: "Tom", age: "20" },
{ id: 2, name: "zhunan", age: "22" },
{ id: 3, name: "kobe", age: "35" }
]
};
}
changeName(event) {
this.setState({
name: event.target.value
});
}
onDelete() {}
render() {
return (
<div>
<ul>
{this.state.details.map((detail, index) => (
<li key={index}>
Name: {detail.name} | age: {detail.age}
<input
style={{ marginLeft: "10px" }}
type="text"
onChange={this.changeName.bind(this)}
value={detail.name}
/>
</li>
))}
</ul>
</div>
);
}
}
export default App;
I updated the code a bit.
First of all, I moved the binding of the callback to the constructor (to have ONE callback instead of one per item*render)
I also changed the key used in the map to be the id, rather than the index of the current item.
Try, it, I hope it works for you.
import React, { Component } from "react";
import "./App.css";
class App extends Component {
constructor(props) {
super(props);
this.state = {
details: [
{ id: 1, name: "Tom", age: "20" },
{ id: 2, name: "zhunan", age: "22" },
{ id: 3, name: "kobe", age: "35" }
]
};
this.changeName = this.changeName.bind(this);
}
changeName(event) {
const {target} = event;
const id = Number(target.dataset.id);
const { details } = this.state;
this.setState({
details: details.map((detail) => {
if (detail.id === id) {
return {
...detail,
name: target.value,
}
}
return detail;
}),
});
}
onDelete() {}
render() {
return (
<div>
<ul>
{this.state.details.map(({ id, age, name }) => (
<li key={id}>
Name: {name} | age: {age}
<input
style={{ marginLeft: "10px" }}
type="text"
onChange={this.changeName}
data-id={id}
value={name}
/>
</li>
))}
</ul>
</div>
);
}
}
export default App;
Your code works fine, nothing wrong with the input data binding. The problem is you're setting the name property directly to the state object. That would make it go from this:
this.state = {
details: [
{ id: 1, name: "Tom", age: "20" },
{ id: 2, name: "zhunan", age: "22" },
{ id: 3, name: "kobe", age: "35" }
]
}
To this:
this.state = {
details: [
{ id: 1, name: "Tom", age: "20" },
{ id: 2, name: "zhunan", age: "22" },
{ id: 3, name: "kobe", age: "35" }
],
name: "Bob"
}
Which has no effect on how the component gets rendered. To properly change the name of one of the details, which is what I assume you want, you also need to do a find that detail object to modify. Like this:
changeName(e, target_detail) {
this.setState({
// always update the WHOLE STATE OBJECT! using a map
details: this.state.details.map(detail => {
// the detail we want to modify has the same ID
if(detail.id === target_detail.id) {
// modify the name value of only that
target_detail.name = e.target.value
}
})
});
}
render method:
render() {
return (
<div>
<ul>
{this.state.details.map((detail, index) => (
<li key={index}>
Name: {detail.name} | age: {detail.age}
<input
style={{ marginLeft: "10px" }}
type="text"
// arrow functions implicitly "bind" the current this context:
onChange={e => this.changeName(e, detail)}
value={detail.name}
/>
</li>
))}
</ul>
</div>
);
}
Just started with ReactJS and I'm looking for the most efficient code to display the array below in a table structure as described in the 'render' section. I have been using .map to iterate through the users/buttons objects, but with no success yet.
In my code sample below, I want to take the userData array and display the content in separate rows (html table format)ie.
Joe,Smith,[Click 1A],[Click2B] //'Click XX' are buttons
Mary,Murphy,[Click 2A],[Click2B]
How can I achieve this?
Thanks
var MyButton = require('./mybutton.js');
var userData =[{
userButtons: [
[{user: [{ id: 1, lastName: 'Smith', firstName: 'Joe',
buttons: [
{button:[{ id:0, value: "Click 1A" enabled:1}]},
{button:[{ id:1, value: "Click 1B" enabled:1}]}
]
}]}],
[{user: [{ id: 1, lastName: 'Murphy', firstName: 'Mary',
buttons: [
{button:[{ id:0, value: "Click 2A" enabled:1}]},
{button:[{ id:1, value: "Click 2B" enabled:1}]}
]
}]
}]
]}];
var DisplayData = React.createClass({
render: function() {
// render userButtons in a table with data using <MyButton> ie.
// <table>
// <tr><td>Joe</td><td>Smith</td><td>[Click 1A]</td><td>[Click 2A]</td</tr>
// <tr><td>Mary</td><td>Murphy</td><td>[Click 2B]</td><td>[Click 2B]</td></tr>
// </table>
}
}
});
React.render(
<DisplayData tArr = {userData} />
, document.getElementById('content')
);
// mybutton.js
var React = require('react');
module.exports = React.createClass({
render: function() {
return (
<button>{this.props.value}</button>
)
}
});
I would suggest you simplify your userData if possible.. you have quite a bit of extra nested arrays that don't seem to be needed.
Something like this:
var userButtons = [
{
id: 1,
lastName: 'Smith',
firstName: 'Joe',
buttons: [
{
id: 0,
value: "Click 1A",
enabled: 1
}, {
id: 1,
value: "Click 1B",
enabled: 1
}
]
},
{
id: 2,
lastName: 'Murphy',
firstName: 'Mary',
buttons: [
{
id: 0,
value: "Click 2A",
enabled: 1
}, {
id: 1,
value: "Click 2B",
enabled: 1
}
]
}
];
Then it's easy to loop through and return the right elements:
return (
<table>
{
userButtons.map(function(ub) {
var buttons = ub.buttons.map(function(button) {
return (
<td>{button.value}</td>
)
});
return (
<tr>
<td>{ub.firstName}</td>
<td>{ub.lastName}</td>
{buttons}
</tr>
)
})
}
</table>
)
Something like the following might work:
handleClick: function(id, value) {
// do something
},
render: function() {
var rows = userData.userButtons.map(
function(u) {
var buttons = u.buttons.map(
function(b) {
return <Button onClick={function() { this.handleClick(b.id, b.value)}.bind(this);}
enabled={b.enabled===1}>
{b.value}
</Button>;
});
return <tr>
<td>{u.firstName}</td>
<td>{u.lastName}</td>
{buttons}
</tr>;
});
return <table>{rows}</table>;
}
Where I assume you can get Button from something like react-bootstrap.