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
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>
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
Implemented writing data from the form to the array. Everything works. but now I want to implement data output when I click on " SEND ARR "
When I hang up a click on a button, respectively, the data is not displayed since in the function we access event.target.value
Please tell me how to rewrite the line so that I can display data when I click on the button? thank
home.js
import React from "react";
import "./../App.css"
export default class Home extends React.Component {
constructor() {
super()
this.state = {
count: 1,
objexm: '',
inputValue: '',
arr: []
}
this.handleClick = this.handleClick.bind(this);
this.updateInputValue = this.updateInputValue.bind(this);
}
handleClick() {
this.setState({
count: this.state.count + 1
});
};
createMarkup() {
return {
__html: this.state.inputValue
};
};
updateInputValue(event) {
let newArr = this.state.arr;
let newlist = event.target.value;
if (event.target.value) {
newArr.push(newlist)
}
this.setState({
inputValue: newArr
});
event.target.value = '';
};
render() {
return (
<div className="home-header">
<h2>{this.state.count}</h2>
<button onClick={this.handleClick}>Add</button>
<input type='text' name="names" onClick={this.updateInputValue} />
{this.state.arr.map((arrs, index) => {
return (
<li
key={index}
>{arrs}</li>
)
})}
<button>SEND ARR</button>
<ul className="qwe">
<li dangerouslySetInnerHTML={this.createMarkup()}></li>
</ul>
</div>
);
}
}
Store the data from the input into invputValue each time the input is updated and on the click of the button update the arr content with the old values (...arr) plus the current input value (this.state.inputValue) .
To make sure the old values are not deleted the arr is defined at the top of the class let arr = []. If you don't want it there you can instantiate it in the constructer which will run only once. i.e. this.arr = []
let arr = []
class Home extends React.Component {
constructor() {
super()
this.state = {
count: 1,
objexm: '',
inputValue: '',
arr: []
}
this.handleClick = this.handleClick.bind(this);
this.updateInputValue = this.updateInputValue.bind(this);
}
handleClick() {
this.setState({
count: this.state.count + 1
});
};
createMarkup() {
return {
__html: this.state.inputValue
};
};
updateInputValue = e => {
this.setState({ inputValue: e.target.value })
}
displayData = () => {
arr = [...arr ,this.state.inputValue]
this.setState({ arr, inputValue: "" })
}
clearData = () => {
this.setState({ arr: [] })
}
render() {
console.log("this.state.arr:", this.state.arr)
return (
<div className="home-header">
<h2>{this.state.count}</h2>
<button onClick={this.handleClick}>Add</button>
<input type='text' name="names" onChange={this.updateInputValue} value={this.state.inputValue} />
{this.state.arr.map((arrs, index) => {
return (
<li
key={index}
>{arrs}</li>
)
})}
<button onClick={this.displayData}>SEND ARR</button>
<button onClick={this.clearData}>CLEAR ARR</button>
<ul className="qwe">
<li dangerouslySetInnerHTML={this.createMarkup()}></li>
</ul>
</div>
);
}
}
Instead of using onClick on input, use onChange and update value in state i.e. make the input a controlled component . Post that onClick of button take the value from state and push to the array and clear the input value
export default class Home extends React.Component {
constructor() {
super()
this.state = {
count: 1,
objexm: '',
inputValue: '',
arr: []
}
this.handleClick = this.handleClick.bind(this);
this.updateInputValue = this.updateInputValue.bind(this);
}
handleClick() {
this.setState(prevState => ({
count: prevState.count + 1,
inputValue: [...prevState.inputValue, prevState.name],
name: ""
}));
};
createMarkup() {
return {
__html: this.state.inputValue
};
};
updateInputValue(event) {
let newVal = event.target.value;
this.setState({
name: newVal
});
};
render() {
return (
<div className="home-header">
<h2>{this.state.count}</h2>
<button onClick={this.handleClick}>Add</button>
<input type='text' name="names" value={this.state.name} onChange={this.updateInputValue} />
{this.state.arr.map((arrs, index) => {
return (
<li
key={index}
>{arrs}</li>
)
})}
<button>SEND ARR</button>
<ul className="qwe">
<li dangerouslySetInnerHTML={this.createMarkup()}></li>
</ul>
</div>
);
}
}
I have a basic 'wrapper' component which contains child 'item' components
class Wrapper extends React.Component {
constructor(props) {
super(props);
this.state = {
items: []
};
}
render() {
return (
<div>Items count- {this.state.items.length}
{this.state.items.map(function (item, i) {
<Item itemId={item.itemId} />
})}
</div>
);
}
}
class Item extends React.Component {
constructor(props) { super(props); }
render() {
return (
<div class="item">{this.props.itemId}</div>
);
}
}
Do I call setState({ "items":[{ "itemId": 22 }] }); to update items in UI?
Want to add/remove 'item' and get UI updated accordingly.
For updates, you want to do something like the following...
// Update item
this.setState({ "items":this.state.items.map(function(item) {
if (item.itemId !== 22) return item;
// update item here
// remember to return item
return item
})
});
// Remove item
this.setState({ "items":this.state.items.filter(item => {
return item.itemId !== 22
})
})
// Add item
this.setState({ "items": this.state.items.concat(newItem)
})
I suggest putting these into React class methods though.
import React from 'react';
class Wrapper extends React.Component {
constructor(props) {
super(props);
this.state = {
items: []
};
this.addItem = this.addItem.bind(this)
this.removeItem = this.removeItem.bind(this)
this.updateItem = this.updateItem.bind(this)
}
addItem (item) {
this.setState({
items: this.state.items.concat(item)
})
}
updateItem(id, updatedItem) {
this.setState({
items: this.state.items.map(function (item) {
if (item.itemId !== id) return item;
return updatedItem;
})
})
}
removeItem(id) {
this.setState({
items: this.state.items.filter(function(item) {
return item.itemId !== id
})
})
}
render() {
return (
<div>Items count- {this.state.items.length}
{this.state.items.map(function (item, i) {
<Item itemId={item.itemId} />
})}
</div>
);
}
}
class Item extends React.Component {
constructor(props) { super(props); }
render() {
return (
<div class="item">{this.props.itemId}</div>
);
}
}
State is not mutable, so the code you've shown there will replace items with an array with one object. If you'd like to add/remove from the array, you'll first need to copy the array somehow , and replace with the new one. You should use the function argument of setState for that. Ex:
this.setState(function (currentState) {
return {items: currentState.concat({itemId: 22})}
});
This is how you add and remove to and from the state items array:
class Wrapper extends React.Component {
constructor(props) {
super(props);
this.state = {
items: []
};
}
addItems = (id) => {
// copies all current items and adds a new one
this.setState({items: [...this.state.items, {itemId: id}]
})
}
removeItems = (id) => {
const newItemList = this.state.items.filter((item) => item.itemId !== id)
this.setState({items: newItemList
})
}
render() {
return (
<div>
<div>Items count - {this.state.items.length}
<button onClick={() => this.addItems(this.state.items.length + 1)}>Add Item</button>
</div>
{
this.state.items.map((item) => {
return (
<Item key={item.itemId} itemId={item.itemId} removeItems={this.removeItems} />
)
})
}
</div>
);
}
}
ReactDOM.render(<Wrapper />, document.getElementById('root'))
class Item extends React.Component {
constructor(props) {
super(props);
}
render() {
return <div className="item">test{this.props.itemId} <button onClick={() => this.props.removeItems(this.props.itemId)}>Remove Item</button></div>;
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id='root'></div>
i have a problem - i need to change parent component's state from child component. I tried standard variant with props, but it's not helping in my case.
Here is code of Parent component:
class ThemesBlock extends React.Component {
constructor(props){
super(props);
this.state = {
currentThemeId: 0
}
}
changeState(n){
this.setState({currentThemeId: n})
}
render() {
let { data } = this.props;
return (
data.map(function (item) {
return <Theme key={item.id} data={item} changeState=
{item.changeState}/>
})
)
}
}
And here is my code for Child Component:
class Theme extends React.Component {
constructor(props){
super(props);
this.changeState = this.props.changeState.bind(this);
}
render() {
const { id, themename } = this.props.data;
const link = '#themespeakers' + id;
return (
<li><a href={link} onClick={() => this.changeState(id)}
className="theme">{themename}</a></li>
)
}
}
The primary issue is that changeState should be bound to the ThemesBlock instance, not to the Theme instance (by ThemesBlock).
Here's an example where I've bound it in the ThemesBlock constructor (I've also updated it to show what theme ID is selected):
class ThemesBlock extends React.Component {
constructor(props) {
super(props);
this.state = {
currentThemeId: 0
}
this.changeState = this.changeState.bind(this);
}
changeState(n) {
this.setState({currentThemeId: n})
}
render() {
let { data } = this.props;
return (
<div>
<div>
Current theme ID: {this.state.currentThemeId}
</div>
{data.map(item => {
return <Theme key={item.id} data={item} changeState={this.changeState} />
})}
</div>
)
}
}
class Theme extends React.Component {
constructor(props) {
super(props);
this.changeState = this.props.changeState.bind(this);
}
render() {
const {
data: {
id,
themename
},
changeState
} = this.props;
const link = '#themespeakers' + id;
return (
<li>{themename}</li>
)
}
}
const data = [
{id: 1, themename: "One"},
{id: 2, themename: "Two"},
{id: 3, themename: "Three"}
];
ReactDOM.render(
<ThemesBlock data={data} />,
document.getElementById("root")
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.4.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.4.2/umd/react-dom.production.min.js"></script>
Did you try to move map() before return? Like this:
render() {
let { data } = this.props;
const outputTheme = data.map(function (item) {
return <Theme key={item.id} data={item} changeState= {item.changeState}/>
})
return (
{outputTheme}
)
}