Adam White Adam White - 1 month ago 11
React JSX Question

Can't re-render after update in React

I have a simple database in React where updates and deletions to the database are handled via Ajax.

After the successful Ajax call on the record component, the updated or deleted record is passed to the records component, which displays all of the records.

There, the handle methods below update the state of the records and re-render the results.

My issue is that while the handleDelete method works fine (and re-renders the records without the deleted record), the handleUpdate method does not and leaves the un-updated record until I refresh the page.

Any ideas why the setState in the handleUpdate wouldn't re-render the results?

Below is the code:

class Sites extends React.Component {
constructor (props) {
super(props);
this.handleUpdate = this.handleUpdate.bind(this);
this.handleDelete = this.handleDelete.bind(this);
this.state = {records: this.props.records};
}
handleUpdate (record) {
let records = this.state.records;
let index = records.map((x) => {return x.id}).indexOf(record.id);
records.splice(index, 1, record);
this.setState({records: records}); # this doesn't re-render the results
}
handleDelete (record) {
let records = this.state.records;
let index = records.map((x) => {return x.id}).indexOf(record.id);
records.splice(index, 1);
this.setState({records: records}); # this re-renders the results
}
render () {
let records = this.state.records.map((record) => {
return (
<Record record={record} key={record.id} handleUpdateRecord={this.handleUpdate} handleDeleteRecord={this.handleDelete}/>
);
});
return (
<div className="col-sm-9">
<table className="table table-striped">
<thead>
<tr>
<th>Name</th>
<th>City</th>
<th>State</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{records}
</tbody>
</table>
</div>
);
}
}

Answer

You should always treat the state as immutable and never, ever modify it directly. Copy its contents, modify the copy and set it again.

I have prepared a pen where I have improvised an implementation for your <Record /> and rewritten the onUpdate and onDelete logic, treating the state as immutable, creating a new records array by either mapping or filtering the old one and setting it again. See it here. I have renamed a couple of things, but it's mostly the same!

These are the important bits:

handleUpdate (record) {
    const records = this.state.records.map(r => { 
        if (r.id !== record.id) return r; 
        return record; 
    })

    this.setState({records})
}

We receive the updated record and map the old array: if we find the element with the sameid as the new one, we return the latter, otherwise we return the original element. map returns a new array, so we are safe.

handleDelete (id) {
    const records = this.state.records.filter(r => r.id !== id)

    this.setState({records})
}

Here we just need the id, and we filter the records array on the state, leaving out the element with the id we want to delete. Again, filter returns a new array, so we are good.

Hope this helps!