Reputation: 482
In the parent component, I receive data from the server and then map this data into a jsx format. Inside this mapping I have a child component and try to pass a value from state of parent to child as a property, however when I update state of this value, the render function for child is not executed.
Expected behavior: As a user I see a list of items. If I click on an item it should become as checked.
export class ReactSample extends React.Component {
constructor(props){
super(props);
this.state = {
items: [],
mappedItems: [],
selectedIds: [],
isSelected: false,
clickedTripId: null
};
this.toggleSelection = this.toggleSelection.bind(this);
}
componentWillMount(){
console.log("Component mounting")
}
toggleSelection (id, e) {
if(!_.includes(this.state.selectedIds, id)) {
this.setState((state) => ({selectedIds:
state.selectedIds.concat(id)}));
this.setState(() => ({clickedTripId: id}));
this.mapItems(this.state.items);
}
}
componentDidMount() {
const self = this;
MyService.getItems()
.then(res => {
self.setState(() => ({ items: res.allItems }));
self.setState(() => ({ mappedItems:
this.mapItems(res.allItems) }));
}
)
}
mapItems (items) {
return items.map(trip => {
return (
<li key={trip.id} onClick={(e) => (this.toggleSelection(trip.id,
e))}>
<span>{trip.title}</span>
<Tick ticked={this.state.clickedTripId}/>
<span className="close-item"></span>
</li>
);
});
}
getItems() {
}
render() {
return (
<div>
<a className="title">This is a react component!</a>
<Spinner showSpinner={this.state.items.length <= 0}/>
<div className="items-container">
<ul id="itemsList">
{this.state.mappedItems}
</ul>
</div>
</div>
);
}
}
export class Tick extends React.Component {
constructor(props) {
super(props);
}
render() {
console.log('RENDER');
return (<span className={this.props.ticked ? 'tick display' :
'tick hide' }></span>);
}
}
Upvotes: 0
Views: 36
Reputation: 482
As Ryan already said, the problem was that mappedItems
where not updated when toggleSelection
was clicked. As it is obvious from the code mapItems
returns data in jsx format. To update it I had to call this.setState({mappedItems: this.mapItems(this.state.items)})
which means that I call mapItems
and then I assign the result to the state. In this case my list will be updated and Tick
component will receive this.state.clickedItemId
as a tick
property. There is one more issue that needs to be done to make this code working:
this mapped list needs to be updated after this.state.clickedItemId
is updated. The method setState
is asynchronous which means that this.setState({mappedItems: this.mapItems(this.state.items)})
has to be called only after this.state.clickedItemId
is updated. To achieve this, the setState
method can receive a callback function as a second parameter. The code snippet is the following:
toggleSelection (id, e) {
if(!_.includes(this.state.selectedIds, id)) {
this.setState((state) => ({
clickedItemId: id,
selectedIds: state.selectedIds.concat(id)
}), () => this.setState({mappedItems: this.mapItems(this.state.items)}));
}
}
In this case, at the time the mapItems
function is executed all data from the state that is needed here will be already updated:
mapItems (items) {
return items.map(item => {
return (
<li key={item.id} onClick={(e) => (this.toggleSelection(item.id, e))}>
<span>{item.title}</span>
<span>{this.state.clickedItemId}</span>
<Tick ticked={this.state.clickedItemId === item.id}/>
<span className="close-item"></span>
</li>
);
});
}
Upvotes: 0
Reputation: 80966
I see a couple issues.
In toggleSelection
you aren't doing anything with the result of mapItems. This kind of bug would be much easier to avoid if you just remove mappedItems
from state and instead just call mapItems
within your render method.
The other issue is you are passing this.state.clickedTripId
as the ticked
property. I assume you meant to pass something more like this.state.clickedTripId === trip.id
.
Upvotes: 1