mgp mgp - 2 months ago 13
React JSX Question

Modify parent component state from child

I am very new to React (so this may be very trivial) and while trying it out on a simple app I am stuck when trying to change a state in a parent component from within a child.

For the app to work, the parent component has to keep the state as it is then passed on to other components. Therefore, I have created a state changing function that I pass down to the child. See the code below:

js:

class App extends React.Component {
constructor(props) {
super(props);
this.state = {
status: [
{
id: 1,
name: 'Item 1',
selected: false
}, {
id: 2,
name: 'Item 2',
selected: false
}]
}
}

addItem(name) {
let id = this.props.status.length + 1;
let newItem = this.props.status;
newItem.push({id, name, selected: true});
this.setState({
status: newItem
});
}

render() {
return (
<div><ItemList status={this.state.status} addItem={this.addItem} /></div>
);
}
}

class ItemList extends React.Component {
constructor(props) {
super(props);
this.state = {
search:'',
};
}

handleAddSubmit(event) {
event.preventDefault();
let name = this.refs.newit.value;
this.props.addItem(name);
this.refs.newit.value='';
}

updateSearch(event) {
this.setState({search: event.target.value});
}

render() {
let filteredItems = this.props.status.filter((item)=> {
return item.name.toLowerCase().indexOf(this.state.search.toLowerCase()) !== -1;
});
return (
<div>
<div>
<input placeholder="Search" type="text"
value={this.state.search}
onChange={this.updateSearch.bind(this)}/>
</div>
<div>
{filteredItems.map((item) => {
return (<button id={item.id}>{item.name}</button>
);
})}
<form onSubmit={this.handleAddSubmit.bind(this)}>
<input type="text" placeholder="enter your item" ref="newit"/>
</form>
</div>
</div>
);
}
}


ReactDOM.render(<App/>, document.getElementById('app'));


html:

<div id="app"></div>


I understand how the code is wrong, the addItem function cannot access the state, but I'm not sure how to fix it. I've tried to pass the state from the parent down to the child as a prop and then back up as a variable to addItem, but that doesn't work either (see code below). Additionally, I've played around with .bind(this) and .bind(null) without success (I don't really understand the difference between the React auto and the this binds).

addItem(oldStatus, name) {
let id = oldStatus.length + 1;
let newItem = oldStatus;
newItem.push({id, name, selected: true});
this.setState({
status: newItem
});
}


I'm not sure if I am trying to do something that is just not possible, not advisable or if its just a simple error. I've checked many of the questions here and the official tutorial but they just setState using parameters passed on to the function as opposed to trying to access the state (to calculate id in my case). I also looked into various tutorials as I believe this to be basic concept but haven't found a solution.

Thank you for your help.

Best,

M

Edit: I have created a jsfiddle with the code: https://jsfiddle.net/magp/cw8h2pzk/4/

Answer

You can open the console to see the errors.

One error was about not binding your addItem with .bind(this).

You should bind this to your passed methods. They might be called on dom, or in other components, so the context of this changes. However if you bind this while you are passing, context of this will be the component. Your this.state... statements will work as expected.

Another error is every child should have a unique key.

It means if you are mapping an array or in short iterating over a data and returning jsx, every jsx you return should have a unique key.

Here is the working jsfiddle. The fields I have changes are the following.

<form onSubmit={this.handleAddSubmit.bind(this)}>

{filteredItems.map((item) => {
   return (<button id={item.id} key={item.id}>{item.name}</button );
})}

https://jsfiddle.net/8v2cu95x/1/

Comments