I want to increment the value on Items component on a button click which is handled by its child component Item. Items have an array of key-value pairs and this value needs to increment and render it
Here is the code
//parent component
class Items extends React.Component{
state={
items:[{ id: 1, value: 9 },
{ id: 2, value: 10 },
{ id: 3, value: 0 }]
}
handleIncrement=()=>{
//need to increment items.value on each button click increment. How can I access it
}
render(){
return(
<div>
<h2>Increment item on the list From Parent</h2>
{this.state.items.map(item=>(<Item key={item.id}
value={item.value} id={item.id} onIncrement={this.handleIncrement}
/>))}
</div>
)
}
}
//child component
class Item extends React.Component{
getValue=()=>{
let {value}=this.props;
return value===0?'Zero':value
}
render(){
return(
<div>
<span>{this.getValue()}</span>
<button onClick={this.props.onIncrement}>Increment</button>
</div>
)
}
}
please help me with this.
You might add id as button name
<button name={this.props.id} onClick={this.props.onIncrement}>Increment</button>
and then use it at your function
handleIncrement= e =>
this.setState({ items: this.state.items.map(item =>
item.id == e.target.name ? {...item, value: item.value++ } : item ) })
Or you can update by array index instead of object id
//parent component
class Items extends React.Component {
state = {
items: [{ id: 1, value: 9 }, { id: 2, value: 10 }, { id: 3, value: 0 }]
};
handleIncrement = e => {
//need to increment items.value on each button click increment.How can i access it
const id = e.target.id;
const tempItems = this.state.items;
tempItems[id] = {...tempItems[id], value: ++tempItems[id].value}
this.setState((prevState)=>({ items: tempItems}));
};
render() {
return (
<div>
<h2>Increment item on the list From Parent</h2>
{this.state.items.map((item,i) => (
<Item
key={item.id}
value={item.value}
id={item.id}
index={i}
onIncrement={this.handleIncrement}
/>
))}
</div>
);
}
}
//child component
class Item extends React.Component {
getValue = () => {
let { value } = this.props;
return value === 0 ? "Zero" : value;
};
render() {
return (
<div>
<span>{this.getValue()}</span>
<button id={this.props.index} onClick={this.props.onIncrement}>Increment</button>
</div>
);
}
}
state = {
items: [{ id: 1, value: 9 }, { id: 2, value: 10 }, { id: 3, value: 0 }]
};
handleIncrement = id => event => {
event.preventDefault();
const s = JSON.parse(JSON.stringify(this.state.items)); // dereference
const ndx = s.map(e => e.id).indexOf(id);
s[ndx]["value"]++;
this.setState({ items: s });
};
here's a sandbox you can preview with the implementation:
https://codesandbox.io/s/wonderful-voice-kkq7b?file=/src/Increment.js:380-803
Related
My code below shows my current component design. This is a counter component which is responsible for incrementing a counter for the respective array item and also for adding the clicked item to the cart. I am trying to figure out if there is some way in which I can assign each array item within the items array to its own state count value. Currently, the screen shows four array items, with each one having a button next to it and also a count. When clicking the increment button for any particular item, the state count for all buttons is updated and rendered, which is not what I want. I have tried to assign each button it's own state count in several ways, but haven't been able to figure out the right way. I would like to somehow bind a state count value to each button so that each one has it's individual state count.I would really appreciate if someone can provide some tips or insight as I dont know of a way to isolate the state count for each button and make it unique so that when one value's button is clicked, only the state count for that particular button (located next to the increment button) is updated and not the others.
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0,
cart: [],
};
}
handleIncrement = (e) => {
this.setState({
count: this.state.count + 1,
cart: [...this.state.cart, e.target.value],
});
};
render() {
const listItems = this.props.items.map((item) => (
<li key={item.id}>
{item.value}
<button onClick={this.handleIncrement}>+</button>
{this.state.count}
</li>
));
return (
<div>
{listItems}
</div>
);
}
}
What I did here is I remove the constructor, update Counter component props, update the event on how to update your cart in Example component, adjusted the Counter component, for the Cart component, I added componentDidMount and shouldComponentUpdate make sure that the component will re-render only when props listArray is changing. Here's the code.
class Example extends React.Component {
state = {
cart: [],
items: [
{ id: 1, value: "L1" },
{ id: 2, value: "L2" },
{ id: 3, value: "L3" },
{ id: 4, value: "L4" }
]
}
render() {
const { cart } = this.state
return (
<div>
<h1>List</h1>
{ items.map(
({ id, ...rest }) => (
<Counter
key={ id }
{ ...rest }
cart={ cart }
onAddToCard={ this.handleAddCart }
/>
)
) }
</div>
)
}
handleAddCart = (item) => {
this.setState(({ items }) => ([ ...items, item ]))
}
}
class Counter extends React.Component {
state = {
count: 0
}
handleIncrement = () => {
this.setState(({ count }) => ({ count: count++ }))
}
render() {
const { count } = this.state
const { cart, value } = this.props
return (
<div>
{ value }
<span>
<button onClick={ this.handleIncrement }>+</button>
{ count }
</span>
<Cart listArray={ cart } />
</div>
)
}
}
class Cart extends React.Component {
state = {
cart: []
}
addTo = () => (
<div>List: </div>
)
componentDidMount() {
const { cart } = this.props
this.setState({ cart })
}
shouldComponentUpdate({ listArray }) {
return listArray.length !== this.state.cart.length
}
render() {
return (
<div>
<ListFunctions addClick={ this.addTo } />
</div>
)
}
}
const ListFunctions = ({ addClick }) => (
<div>
<button onClick={ addClick }>Add To List</button>
</div>
)
If you want to add to the list of items without rendering the button, you can add a custom property to mark that it is a custom addition:
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
items: [
{ id: 1, value: "L1" },
{ id: 2, value: "L2" },
{ id: 3, value: "L3" },
{ id: 4, value: "L4" },
]
}
}
addToItems = items => {
this.setState({
items,
});
}
render() {
var cartArray = [];
return (
<div>
<h1>List</h1>
{this.state.items.map((item) =>
<Counter
key={item.id}
value={item.value}
id={item.id}
custom={item.custom}
cart={cartArray}
addToItems={this.addToItems}
items={this.state.items}
/>
)}
</div>
);
}
}
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0,
};
}
handleIncrement = () => {
this.setState({
count: this.state.count + 1,
});
this.props.cart.push(this.props.value);
};
addTo = () => {
const { items } = this.props;
let lastId = items.length;
lastId++;
this.props.addToItems([
...items,
{
id: lastId,
value: `L${lastId}`,
custom: true,
}]);
};
render() {
return (
<div>
{this.props.value}
{
!this.props.custom &&
(
<span>
<button onClick={this.handleIncrement}>+ </button>
{this.state.count}
</span>
)
}
<Cart addTo={this.addTo} />
</div>
);
}
}
class Cart extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<ListFunctions
addClick={this.props.addTo}
/>
</div>
);
return null;
}
}
const ListFunctions = ({ addClick}) => (
<div>
<button onClick={addClick}>Add To List</button>
</div>
);
// Render it
ReactDOM.render(
<Example />,
document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="react"></div>
Parent component
class Parent extends Component {
constructor(props) {
super(props);
this.state = {
checkedView:[
{id: 1, value: "A", isChecked: false},
{id: 2, value: "B", isChecked: true},
{id: 3, value: "C", isChecked: true},
{id: 4, value: "D", isChecked: true}
],
}
}
handleCheck=(e)=>{
this.setState({ isChecked: e.target.checked});
}
render(){
return(
<div>
<Selection checkedView={this.state.checkedView} handleCheck={this.handleCheck} />
<Content checkedView={this.state.checkedView} />
</div>
);
}
}
Selection component
class Selection extends Component {
constructor(props) {
super(props)
this.state = {
checkedView: this.props.checkedView
}
}
handleCheck = (event) => {
let checkedView = this.props.checkedView;
checkedView.forEach( item => {
if(item.value === event.target.value){
item.isChecked = event.target.checked
}
})
this.setState({
checkedView: checkedView
})
this.props.handleCheck(event)
}
render() {
return (
<div className="">
<ul className="morefeatures">{
this.props.checkedView.map((selection, index) => {
return (<CheckBox key={index} handleCheck={this.handleCheck} {...selection} />)
})
}
</ul>
</div>
);
}
}
CHECKBOX
export const CheckBox = props => {
return (
<li>
<input key={props.id} onClick={props.handleCheck} type="checkbox" checked={props.isChecked} value={props.value} /> {props.value}
</li>
)
}
I have a parent component that controls the content, and the selection component should be able to update its change to the parent component so other child components then can access the value of the checkbox.
When a (or multiple) checkboxes are checked, Content Component receives signal to display relevant content, something like that
The isChecked not change its status no mater the checkbox is checked or not checked.
So how should I modify the code to make the isChecked really work?????
You are not updating the right state. this.setState({ isChecked: e.target.checked}); adds a new variable isChecked to the state. What you want is find the right entry within checkedView and update that object. I would pass down the id to the checkbox and on onClick, I would call handleChecked which receives not only the event but also the id of the checkbox. In handleChecked you can then find the right checkbox based on the id and update that one accordingly.
In parent:
handleCheck=(id, checked)=>{
// deep copy old state (check out lodash for a nicer deepCopy)
const checkedView = JSON.parse(JSON.stringify(this.state.checkedView));
const checkBox = checkedView.find(view => view.id === id);
checkBox.isChecked = checked;
// update whole object of new state
this.setState(checkedView);
}
In Selection:
!Warning! You were altering props, never update props, that's the job of the parent. You also don't need to put the checkedView in the state in Selection, you receive it as a prob, just pass it down.
handleCheck = (event, id) => {
this.props.handleCheck(id, e.target.checked)
}
class Selection extends Component {
constructor(props) {
super(props)
}
handleCheck = (event, id) => {
this.props.handleCheck(id, e.target.checked)
}
render() {
return (
<div className="">
<ul className="morefeatures">{
this.props.checkedView.map((selection) => {
return (<CheckBox key={selection.id} handleCheck={this.handleCheck} {...selection} />)
})
}
</ul>
</div>
);
}
}
In Checkbox, wrap the handleCheck to pass it both the event and the id to identify the checkbox.
<input onClick={(e) => props.handleCheck(e, props.id)} type="checkbox" checked={props.isChecked} value={props.value} /> {props.value}
If I understand your question I think this is what you're looking for - your architecture and logic are a bit off and you've coded yourself into a corner...- Two components (Parent and Child)
Here is your Parent: (keeps the state and all methods that manipulate it)
export default class Parent extends React.Component {
state = {
checkedView: [
{ id: 1, value: 'A', isChecked: false },
{ id: 2, value: 'B', isChecked: true },
{ id: 3, value: 'C', isChecked: true },
{ id: 4, value: 'D', isChecked: true }
]
};
handleCheck = (id) => {
this.setState({
checkedView: this.state.checkedView.map((item) => {
if (item.id === id) {
return {
...item,
isChecked: !item.isChecked
};
} else {
return item;
}
})
});
};
render() {
return (
<div>
{this.state.checkedView.map((item) => (
<Child key={item.id} item={item} handleCheck={this.handleCheck} />
))}
</div>
);
}
}
Here is your Child:
import React from 'react';
export default function Child({ item, handleCheck }) {
return (
<div onClick={() => handleCheck(item.id)}>
{item.value}
<input type='checkbox' defaultChecked={item.isChecked} />
</div>
);
}
Here is a live demo: https://stackblitz.com/edit/react-ddqwpb?file=src%2FApp.js
I'm guessing you can understand what the code is doing... if not ask...
I am trying to play around with react's child to parent communication, i am passing three buttons which has unique ids, I want to simply display the values after increment button is clicked. On first click, every button does increment fine, however, after second click on any button it gives
Cannot read property 'value' of undefined
. I am not sure what is happening after first click.
let data = [
{id: 1, value: 85},
{id: 2, value: 0},
{id: 3, value: 0}
]
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {
counter: 1,
data: this.props.data
}
}
componentWillReceiveProps(nextProps) {
if(nextProps !== this.state.data) {
this.setState({data: nextProps})
}
}
handleClick(id) {
this.props.increment(id,
this.state.data[id-1].value = this.state.data[id-1].value +
this.state.counter);
}
render() {
return (
<div>
{data.map(data => {
return (
<div key={data.id}>
{data.value}
<button onClick={() => this.handleClick(data.id)}>+</button>
</div>
)
})}
</div>
)
}
}
class App extends React.Component {
constructor() {
super();
this.state = {
data
}
}
onIncrement(id, newValue) {
this.setState((state) => ({
data: state.data[id-1].value = newValue
}))
}
render() {
return (
<div>
<Counter data={this.state.data} increment={this.onIncrement.bind(this)}/>
</div>
)
}
}
ReactDOM.render(
<App />,
document.querySelector("#root")
)
At this sentence:
this.props.increment(id,
this.state.data[id-1].value = this.state.data[id-1].value +
this.state.counter);
You are doing id-1, i think you don't need that, just [id].
In case you are click button with id 1 your are trying to increment value of 1 - 1 and you haven't any data with id 0
The problematic thing in your code is this line
this.state.data[id-1].value = this.state.data[id-1].value + this.state.counter);
what exactly you want to do here ? because you have 3 index 0,1,2 and you are out of index the it's undefined and you got error mention your requirement here.
your code using state in useless manner and i just optimize your code in a good way. Tip: do not use state-full component where not required use function component. it's working fine and serve according to your need.
const Counter = (props) => {
return (
<div>
{props.data.map(d => {
return (
<div key={d.id}>
{d.value}
<button onClick={() => props.increment(d.id, (d.value + 1))}>+</button>
</div>
)
})}
</div>
)
}
class App extends React.Component {
state = {
data: [
{ id: 1, value: 85 },
{ id: 2, value: 0 },
{ id: 3, value: 0 }
]
}
onIncrement = (id, newValue) => {
debugger
var newdata = [...this.state.data];
var d = { ...newdata[id - 1] };
d.value = newValue;
newdata[id - 1] = d;
this.setState({ data: newdata })
}
render() {
return (
<div>
<Counter data={this.state.data} increment={this.onIncrement} />
</div>
)
}
}
ReactDOM.render(
<App />,
document.querySelector("#root")
)
import React from 'react';
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {
counter: 1,
data: this.props.data
};
}
handleClick(id, index) {
this.props.increment(
id,
(this.props.data[id - 1].value =
this.props.data[id - 1].value + this.state.counter), index
);
}
render() {
return (
<div>
{this.props.data.map((data
, index) => {
return (
<div key={data.id}>
{data.value}
<button onClick={() => this.handleClick(data.id, index)}>+</button>
</div>
);
})}
</div>
);
}
}
export default class App extends React.Component {
constructor() {
super();
this.state = {
data: [{ id: 1, value: 85 }, { id: 2, value: 0 }, { id: 3, value: 0 }]
};
}
onIncrement(id, newValue, index) {
let {data} = this.state;
data[index].value = newValue;
this.setState({
data
});
}
render() {
console.log(this.state.data)
return (
<div>
<Counter
data={this.state.data}
increment={this.onIncrement.bind(this)}
/>
</div>
);
}
}
please take a look you are doing state updation in wrong way
I found the issue, you have wrongly implemented componentWillReceiveProps and onIncrement , i have corrected these two functions :
onIncrement(id, newValue) {
let data = this.state.data;
data[id-1].value = newValue;
this.setState({data})
}
componentWillReceiveProps(nextProps) {
if(nextProps.data !== this.props.data) {
this.setState({data: nextProps.data})
}
}
Also see the working demo here : https://repl.it/#VikashSingh1/TemporalReliableLanserver
I am new to react and I've been trying to achieve a function that I am not sure of, I have a component that renders JSON file and shows products named 'product list', another component named 'person' which is used to show product items, both are working fine, but the third component called menucat includes the scrolling menu from https://www.npmjs.com/package/react-horizontal-scrolling-menu, the onselect function of the menu component returns an id number on selection, I want to pass that number inside the mapping function within the productlist.
Product list
import React from "react";
import Person from "./Person";MenuCat";
import MenuCat, {a, onSelect, selected} from "../components/
class ProductList extends React.Component {
state = {
error: null,
isLoaded: false,
items: []
};
componentDidMount() {
fetch("items.json")
.then(res => res.json())
.then(
result => {
this.setState({
isLoaded: true,
items: result
});
},
error => {
this.setState({
isLoaded: true,
error
});
}
);
}
render() {
const { error, isLoaded, items } = this.state;
if (error) {
return (
<div>
Error:{" "}
{error.message }
{console.log("check 1:", items)}
</div>
);
} else if (!isLoaded) {
return (
<div>
<img
src="loading.gif"
alt="loading"
height="100"
/>
</div>
);
} else {
return (
<div>
<MenuCat />
<div className="row">
{items.children[0].children.map(item => (
<Person
className="person"
Key={item.name}
Title={item.name}
imgSrc={item.image_url}
>
{item.base_price}
</Person>
))}
</div>
</div>
);
}
}
}
and the menuCat component looks like this
import React, { Component } from "react";
import ScrollMenu from "react-horizontal-scrolling-menu";
import "../menu.css";
// list of items
const list = [
{ name: "category1" , id : 0},
{ name: "category2" , id : 1},
{ name: "category3" , id : 2},
{ name: "category4" , id : 3},
{ name: "category5" , id : 4},
{ name: "category6" , id : 5},
{ name: "category7" , id : 6},
];
// One item component
// selected prop will be passed
const MenuItem = ({ text, selected }) => {
return <div className="menu-item">{text}</div>;
};
// All items component
// Important! add unique key
export const Menu = list =>
list.map(el => {
const { name } = el;
const { id } = el;
return <MenuItem text={name} key={id} />;
});
const Arrow = ({ text, className }) => {
return <div className={className}>{text}</div>;
};
const ArrowLeft = Arrow({ text: "<", className: "arrow-prev" });
const ArrowRight = Arrow({ text: ">", className: "arrow-next" });
export class Menucat extends Component {
state = {
selected: "0"
};
onSelect = key => {
console.log(`onSelect: ${key}`);
this.setState({ selected: key});
};
render() {
const { selected } = this.state;
// Create menu from items
const menu = Menu(list, selected);
return (
<div className="App">
<ScrollMenu
data={menu}
arrowLeft={ArrowLeft}
arrowRight={ArrowRight}
selected={selected}
onSelect={this.onSelect}
/>
</div>
);
}
}
export default Menucat;
I want the id generated from onselect function to be added instead of 0 in
{items.children[0].children.map(item => (
so that whenever the user clicks on a category item, the id of that category goes to the mapping function which will do the rest. I am aware that the category list is hardcoded, for now, I just want this communication between the components to happen, I want to pass the id from the menucat component to a something like a variable in product list that can go instead of the zero like {items.children[selected].children.map(item => (
you'll want to add an 'onSelect' prop to MenuCat to pass through the ScrollMenu's onSelect results, then a state value in ProductList to store the selected key. something like this:
ProductList
import React from "react";
import Person from "./Person";MenuCat";
import MenuCat, {a, onSelect, selected} from "../components/
class ProductList extends React.Component {
state = {
error: null,
isLoaded: false,
items: [],
selected: 0,
};
componentDidMount() {
fetch("items.json")
.then(res => res.json())
.then(
result => {
this.setState({
isLoaded: true,
items: result
});
},
error => {
this.setState({
isLoaded: true,
error
});
}
);
}
constructor(props) {
super(props);
this.onSelect = this.onSelect.bind(this);
}
onSelect(key) {
this.setState({selected: key});
}
render() {
const { error, isLoaded, items } = this.state;
if (error) {
return (
<div>
Error:{" "}
{error.message }
{console.log("check 1:", items)}
</div>
);
} else if (!isLoaded) {
return (
<div>
<img
src="loading.gif"
alt="loading"
height="100"
/>
</div>
);
} else {
return (
<div>
<MenuCat onSelect={this.onSelect} />
<div className="row">
{items.children[this.state.selected].children.map(item => (
<Person
className="person"
Key={item.name}
Title={item.name}
imgSrc={item.image_url}
>
{item.base_price}
</Person>
))}
</div>
</div>
);
}
}
}
and MenuCat like
import React, { Component } from "react";
import ScrollMenu from "react-horizontal-scrolling-menu";
import "../menu.css";
// list of items
const list = [
{ name: "category1" , id : 0},
{ name: "category2" , id : 1},
{ name: "category3" , id : 2},
{ name: "category4" , id : 3},
{ name: "category5" , id : 4},
{ name: "category6" , id : 5},
{ name: "category7" , id : 6},
];
// One item component
// selected prop will be passed
const MenuItem = ({ text, selected }) => {
return <div className="menu-item">{text}</div>;
};
// All items component
// Important! add unique key
export const Menu = list =>
list.map(el => {
const { name } = el;
const { id } = el;
return <MenuItem text={name} key={id} />;
});
const Arrow = ({ text, className }) => {
return <div className={className}>{text}</div>;
};
const ArrowLeft = Arrow({ text: "<", className: "arrow-prev" });
const ArrowRight = Arrow({ text: ">", className: "arrow-next" });
export class Menucat extends Component {
state = {
selected: "0"
};
onSelect = key => {
console.log(`onSelect: ${key}`);
this.setState({ selected: key});
this.props.onSelect(key);
};
render() {
const { selected } = this.state;
// Create menu from items
const menu = Menu(list, selected);
return (
<div className="App">
<ScrollMenu
data={menu}
arrowLeft={ArrowLeft}
arrowRight={ArrowRight}
selected={selected}
onSelect={this.onSelect}
/>
</div>
);
}
}
export default Menucat;
you pass an 'onSelect' function to menucat, menucat calls it when the item is selected, and back in ProductList, its 'onSelect' function is then run, setting state which can then be used in your item selection.
make sense?
I have a bootstrap grid where each grid item is populated from an array of objects but after each grid item I would like to have a vote button. How could I achieve this with maintaining state on each button separately, ie when button 1 is clicked the text should change from 'vote' to 'voted' whilst the others remain as 'vote'.
At the moment when a button is clicked, all of them change to 'Voted'
class Items extends Component {
constructor(props) {
super(props);
this.state = { hasVoted: false };
this.OnClick = this.OnClick.bind(this);
}
OnClick() {
this.setState(prevState => ({
hasVoted: !prevState.hasVoted
}));
}
render() {
const Item = teasers.items.map(item =>
<Col key={item.nid}>
<span>
{itemType}
</span>
<a href={item.path}>
<Image src={item.image.src} title={item.productType} />
<span>
{item.Title}
</span>
<div className={teasersStyle.copy}>
{" "}{item.Copy}>
</div>
</a>
<div
className={this.state.hasVoted ? "active" : "notactive"}
onClick={this.OnClick}
>
{this.state.hasVoted ? "Voted" : "Vote"}
</div>
</Col>
);
return (
<div>
<Grid>
<Row>
{Item}
</Row>
</Grid>
</div>
);
}
}
export default Items;
I have created a simple example for you:
class App extends React.Component {
constructor() {
super();
this.onClick = this.onClick.bind(this);
this.state = {
arr: [
{ name: "first", isActive: true },
{ name: "second", isActive: true },
{ name: "third", isActive: true },
{ name: "fourth", isActive: true }
]
};
}
onClick(index) {
let tmp = this.state.arr;
tmp[index].isActive = !tmp[index].isActive;
this.setState({ arr: tmp });
}
render() {
return (
<div>
{this.state.arr.map((el, index) =>
<div key={index} onClick={() => this.onClick(index)}>
name: {el.name} / isActive: {el.isActive ? "true" : "false"}
</div>
)}
</div>
);
}
}
Check the fiddle and implement it in your case.
One more way to handle this is keeping the index of an active button in the state:
class App extends React.Component {
state = {
users: [
{ name: "John" },
{ name: "Sarah" },
{ name: "Siri" },
{ name: "Jim" },
{ name: "Simon" },
],
activeIndex: 0,
}
render() {
const { users, activeIndex } = this.state;
return (
<div>
{users.map((u, i) => (
<div
className={i === activeIndex ? 'active' : ''}
onClick={() => this.setState({ activeIndex: i })}
key={u.name}
>
{u.name}
</div>
))}
</div>
)
}
}
https://jsfiddle.net/846tfe3u/