I am using Material UI tables to populate a table with some datas taken from my Redux store and then using a function to remove data.
The table I am using is here https://material-ui.com/demos/tables/ and is the one labelled "Sort and selecting". I will just include the part that interest us and omit all the rest.
So when the component mounts, I get the data from the database by dispatching "startGetClients" and I pass the data in the state of the "ClientsListTable" to populate the table. This works fine.
When clicking on the delete "div" (this is actually an Icon in another component but for testing purpose I have used a simple "div") I take an array with the selected ids and in a "forEach" I dispatch "removeClients" passing the object. If I check the state on Redux dev tool I can see that is correctly updated and the object is removed but I can't update the state of ClientsListTable unless I click again despite the fact the I run the setState() function passing the new data. I know it has something to do with ASYNC but I can't figure this out.
class ClientsListTable extends Component {
constructor(props) {
super(props);
this.state = {
order: 'asc',
orderBy: 'name',
selected: [],
data: [],
page: 0,
rowsPerPage: 5,
};
}
componentWillMount() {
this.props.startGetClients().then(() => {
let data = this.props.clients;
this.setState( () =>({ data }) );
});
}
handleSelectAllClick = (event, checked) => {
if (checked) {
this.setState(state => ({ selected: state.data.map(n => n.id) }));
return;
}
this.setState({ selected: [] });
};
handleDeletedData = (selectedIds, data) => {
selectedIds = this.state.selected;
data = this.props.clients;
selectedIds.forEach(id => {
this.props.removeClients({id: id});
});
this.setState( () =>({ data }) );
}
render() {
const { classes } = this.props;
const { data, order, orderBy, selected, rowsPerPage, page } = this.state;
const emptyRows = rowsPerPage - Math.min(rowsPerPage, data.length - page * rowsPerPage);
return (
<Paper className={classes.root}>
<div onClick={this.handleDeletedData}>REMOVE ITEMS</div>
<ClientsListTableToolbar numSelected={selected.length} selectedId={selected} />
<div className={classes.tableWrapper}>
<Table className={classes.table} aria-labelledby="tableTitle">
<ClientsListTableHead
numSelected={selected.length}
order={order}
orderBy={orderBy}
onSelectAllClick={this.handleSelectAllClick}
onRequestSort={this.handleRequestSort}
rowCount={data.length}
/>
<TableBody>
{data
.sort(getSorting(order, orderBy))
.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
.map(n => {
const isSelected = this.isSelected(n.id);
return (
<TableRow
hover
onClick={event => this.handleClick(event, n.id)}
role="checkbox"
aria-checked={isSelected}
tabIndex={-1}
key={n.id}
selected={isSelected}
>
<TableCell padding="checkbox">
<Checkbox checked={isSelected} />
</TableCell>
<TableCell component="th" scope="row" padding="none">
{n.clientName}
</TableCell>
<TableCell>{n.lastLogin ? n.lastLogin : 'Never logged in'}</TableCell>
</TableRow>
);
})}
{emptyRows > 0 && (
<TableRow style={{ height: 49 * emptyRows }}>
<TableCell colSpan={6} />
</TableRow>
)}
</TableBody>
</Table>
</div>
<TablePagination
component="div"
count={data.length}
rowsPerPage={rowsPerPage}
page={page}
backIconButtonProps={{
'aria-label': 'Previous Page',
}}
nextIconButtonProps={{
'aria-label': 'Next Page',
}}
onChangePage={this.handleChangePage}
onChangeRowsPerPage={this.handleChangeRowsPerPage}
/>
</Paper>
);
}
}
const mapStateToProps = (state) => ({
clients: state.clients,
});
const mapDispatchToProps = (dispatch) => ({
startGetClients: () => dispatch(startGetClients()),
removeClients: (data) => dispatch(removeClients(data))
});
export default compose(
withStyles(styles),
connect(mapStateToProps,mapDispatchToProps),
)(ClientsListTable);
// ACTIONS
export const removeClients = ({ id } = {}) => ({
type: 'REMOVE_CLIENTS',
id
});
// REDUCERS
case 'REMOVE_CLIENTS':
return state.filter( ({id}) => id !== action.id);
It is hard to know without a bit more information about your project but I think the following method is possibly to blame:
handleDeletedData = (selectedIds, data) => {
selectedIds = this.state.selected;
data = this.props.clients;
selectedIds.forEach(id => {
this.props.removeClients({id: id});
});
this.setState( () =>({ data }) );
}
What you're doing here is dispatching a remove event for each ID (asynchronously) and then when you're done you explicitly set the state = data
. The problem here is "what is the data at this point in time?" I suspect the first time you call this it sets it using the "data" and when you call it again it sets it using the "new data" from the redux store.
If you change this so that it depends on the store: this.props.clients
rather than the state: this.state.data
it should updated when the reducer updates the store.
Try this, instead of:
const { classes } = this.props;
const { data, order, orderBy, selected, rowsPerPage, page } = this.state;
try
const { classes } = this.props;
const { order, orderBy, selected, rowsPerPage, page } = this.state;
const data = this.props.clients
The just remove the "setState" call in your handleDeletedData
method. When redux updates the store your properties should change and force the table to render.
handleDeletedData = (selectedIds, data) => {
selectedIds = this.state.selected;
data = this.props.clients;
selectedIds.forEach(id => {
this.props.removeClients({id: id});
});
// this.setState( () =>({ data }) );
}