Topic: MDBDataTable data does not render on state change

Matan Tubul free asked 5 years ago


I am trying to delete row from my DataTable, the delete action is recorded correctly on the DB and then i am updating my `this.state.data` object. after the delete this is the message i have in my console:
"0: Object { firstName: "Matan", lastName: "Tubul", userName: "matant86@gmail.com", … }
​​
length: 1"

 

which means the row is deleted, even the message "Showing 1 to 1 of 1 entries" is updated from 2 to 1.

but still i am see 2 rows in my DataTable.

I attached here my code, do you see something that i am missing?

import React, { Component } from 'react'
import { withRouter, Redirect } from 'react-router-dom'
import { MDBDataTable, MDBBtn, MDBIcon, Modal, ModalBody, ModalHeader, ModalFooter, Container, MDBRow, MDBCol } from 'mdbreact';
import { register, getUsersList, deleteUser } from './UserFunctions'


class Register extends Component {
constructor() {
super()
this.state = {
data: {
columns: [
{
label: 'First Name',
field: 'firstName',
sort: 'asc',
width: 150
},
{
label: 'Last Name',
field: 'lastName',
sort: 'asc',
width: 270
},
{
label: 'Email',
field: 'userName',
sort: 'asc',
width: 200
},
{
label: 'Actions',
field: 'actions',
width: 10
}
],
rows: []
},
modalIsOpen: false,
fname: '',
lname: '',
email: '',
modalErrorIsOpen: false,
modalDeleteUserIsOpen: false,
userIndexToDelete:-1,
userToDelete:'',
error: '',
}
this.onChange = this.onChange.bind(this)

}

onChange(e) {
this.setState({ [e.target.name]: e.target.value })
}

toggle = (expression) => {
switch (expression) {
case 'error':
this.setState({
modalErrorIsOpen: !this.state.modalErrorIsOpen
});
break;
case 'addUser':
this.setState({
modalIsOpen: !this.state.modalIsOpen
});
break;
case 'delUser':
this.setState({
modalDeleteUserIsOpen: !this.state.modalDeleteUserIsOpen
});
break;
default:
}

};

shouldComponentUpdate(newProps, nextState) {
// return this.state.data != nextState.data;
return true
}
componentDidUpdate(nextProps, nextState) {
// this.loadUsers()
console.log("did update")
this.render()
}

loadUsers() {
getUsersList().then(res => {
if (res) {
let resData = this.state.data;
resData.rows = res.data;
for (let i = 0; i < resData.rows.length; i++) {
resData.rows[i]['actions'] = this.buildUsersTableActions(i)
}
this.setState({
data: resData
})
}
this.setState({
error: res.error
});
}).catch(err => {
console.log(err)
})
}

verifyDeleteUser = (index) => {
this.setState({
userIndexToDelete: index
})
let user = this.state.data.rows[index].userName
this.setState({
userToDelete: user
})
this.toggle('delUser')

};

deleteUser = () => {
this.toggle('delUser')
let index = this.state.userIndexToDelete
deleteUser(this.state.data.rows[index].userName).then( res => {
if (res) {
if ('error' in res) {
this.setState({
error: res.error
});
this.toggle('error')
}else {
let resData = this.state.data
resData.rows.splice(index,1)
this.setState({
data:resData,
userIndexToDelete: '',
userToDelete:''
})
}

}
}).catch( err => {
console.log(err)
})

};

buildUsersTableActions(index) {
return (
<MDBRow style={{marginLeft:15}}>
<MDBBtn color="primary" size="sm"> <MDBIcon icon="pencil" size="lg" className="mr-2" />Edit</MDBBtn>
<MDBBtn color="danger" size="sm" onClick={() => this.verifyDeleteUser(index)}> <MDBIcon icon="trash" size="lg" className="mr-2" />Delete</MDBBtn>
</MDBRow>
)
}

onSubmit = (e) => {
e.preventDefault()

const user = {
firstName: this.state.fname,
lastName: this.state.lname,
userName: this.state.email
}

register(user).then(res => {
if (res) {
this.toggle('addUser')
if ('error' in res) {
this.setState({
error: res.error
});
this.toggle('error')
} else {
this.props.history.push('/register')
}

}
})
}

async componentDidMount() {
this.loadUsers()
console.log("Did mount")

}

render() {
if (!localStorage.usertoken) {
return (<Redirect to="/login"></Redirect>)
} else {
if (this.state.data.rows.length < 1) {
return <Container style={{ display: 'flex', justifyContent: 'center', marginTop: 300 }}>
<div className="loader center"></div>
</Container>
}
console.log(this.state.data)
console.log("render")
return (
<Container style={{ marginTop: 80 }}>
<Modal isOpen={this.state.modalIsOpen} toggle={() => this.toggle('addUser')}>
<ModalHeader toggle={() => this.toggle('addUser')}>Register new user</ModalHeader>
<ModalBody>
<Container>
<MDBRow>
<MDBCol md="12">
<form className='needs-validation'>
<label className="black-text">
First Name
</label>
<input
type="text"
name="fname"
className="form-control"
onChange={this.onChange}
required

/>
<div style={{ top: 'auto' }} className="invalid-tooltip">Please provide a valid state.</div>
<br />
<label className="black-text">
Last Name
</label>
<input
type="text"
name="lname"
className="form-control"
onChange={this.onChange}
required
/>
<div style={{ top: 'auto' }} className="invalid-tooltip">Please provide a valid state.</div>
<br />
<label className="black-text">
Your email
</label>
<input
type="email"
name="email"
className="form-control"
onChange={this.onChange}
required
/>
<div style={{ top: 'auto' }} className="invalid-tooltip">Please provide a valid state.</div>
<br />

</form>
</MDBCol>
</MDBRow>
</Container>
</ModalBody>
<ModalFooter>
<MDBBtn color="blue-grey" onClick={() => this.toggle('addUser')}>Close</MDBBtn>{' '}
<MDBBtn color="dark-green" onClick={this.onSubmit} >Register</MDBBtn>
</ModalFooter>
</Modal>
<Modal modalStyle="warning" isOpen={this.state.modalErrorIsOpen} toggle={() => this.toggle('error')} size="sm">
<ModalHeader toggle={() => this.toggle('error')}>Failed</ModalHeader>
<ModalBody className="font-weight-bold">
{this.state.error}
</ModalBody>
<ModalFooter>
<MDBBtn color="yellow" size="sm" onClick={() => this.toggle('error')}>Close</MDBBtn>
</ModalFooter>
</Modal>
<Modal modalStyle="danger" isOpen={this.state.modalDeleteUserIsOpen} toggle={() => this.toggle('delUser')} size="sm">
<ModalHeader toggle={() => this.toggle('delUser')}>Failed</ModalHeader>
<ModalBody className="font-weight-bold">
Are you sure you want to delete {this.state.userToDelete}?
</ModalBody>
<ModalFooter>
<MDBBtn color="danger" size="sm" onClick={() => this.deleteUser()}>Delete</MDBBtn>
</ModalFooter>
</Modal>
<MDBBtn color="primary" onClick={() => this.toggle('addUser')} style={{ marginBottom: 10 }}>
<MDBIcon icon="plus" className="mr-1" /> Add User
</MDBBtn>
<MDBDataTable hover
striped
bordered
large="true"
data={this.state.data}
fixed
/>

</Container>
)
}
}
}
export default withRouter(Register)

Anna Morawska staff answered 5 years ago


Hi there, 

I've prepared a small example for you: 

import { MDBDataTable } from 'mdbreact';
import './index.css';
import React, { Component } from 'react';
class DatatablePage extends Component {

state = {
data: {
columns: [
{
label: 'Name',
field: 'name',
sort: 'asc',
width: 150
},
{
label: 'Position',
field: 'position',
sort: 'asc',
width: 270
},
{
label: 'Office',
field: 'office',
sort: 'asc',
width: 200
},
{
label: 'Age',
field: 'age',
sort: 'asc',
width: 100
},
{
label: 'Start date',
field: 'date',
sort: 'asc',
width: 150
},
{
label: 'Salary',
field: 'salary',
sort: 'asc',
width: 100
}
],
rows: [
{
name: 'Tiger Nixon',
position: 'System Architect',
office: 'Edinburgh',
age: '61',
date: '2011/04/25',
salary: '$320'
},
{
name: 'Garrett Winters',
position: 'Accountant',
office: 'Tokyo',
age: '63',
date: '2011/07/25',
salary: '$170'
},

{
name: 'Ashton Cox',
position: 'Junior Technical Author',
office: 'San Francisco',
age: '66',
date: '2009/01/12',
salary: '$86'
},

{
name: 'Cedric Kelly',
position: 'Senior Javascript Developer',
office: 'Edinburgh',
age: '22',
date: '2012/03/29',
salary: '$433'
},
{
name: 'Airi Satou',
position: 'Accountant',
office: 'Tokyo',
age: '33',
date: '2008/11/28',
salary: '$162'
},
{
name: 'Brielle Williamson',
position: 'Integration Specialist',
office: 'New York',
age: '61',
date: '2012/12/02',
salary: '$372'
}
]}
}
componentDidMount() {
let newRows = [...this.state.data.rows];
newRows = newRows.slice(0, -1);
setTimeout(() => {
this.setState({
data: {
...this.state.data,
rows: newRows
}
})
}, 3000);
}
render() {
return (
<MDBDataTable
striped
bordered
hover
data={this.state.data}
/>
);
}
}

export default DatatablePage;

your code is quite complex and hard to analyze, but the main problem which I see is a delete method, particularly this fragment of code: 

let resData = this.state.data
resData.rows.splice(index,1)
this.setState({
data:resData,
userIndexToDelete: '',
userToDelete:''
})

It is considered as an antipattern, and it can causes unexpected behaviour. Please notice, that you save a reference to the this.state.data array is resData variable.  In the next line, you modify this array, because the splice method changes the array, it's not returning a new one like slice method. This means that you modify the state directly, your reference in the resData variable is pointing directly to the array in the state. Please try to adjust it as I've demonstrated in the example above, and let us know if it works.
 
Best,
Ania

dispy surfer free commented 4 years ago

Hey there!I can confirm that this issue arises with mbreact 4.22. It appears to be a bug in MDBDataTable, as

{JSON.stringify(this.state.data)} MDBDataTable data={this.state.data}

clearly shows that this.state.data gets updated correctly and that rendering takes place.The problem can be solved by specifying

MDBDataTable data={{columns: this.state.data.columns, rows : this.state.data.rows}}

This is literally all I changed and now it works like a charm. Appears to be a bug. I'd report it, but I didn't find a bugtracker.

With best regards,



Please insert min. 20 characters.

FREE CONSULTATION

Hire our experts to build a dedicated project. We'll analyze your business requirements, for free.

Status

Answered

Specification of the issue

  • ForumUser: Free
  • Premium support: No
  • Technology: MDB React
  • MDB Version: 4.8.4
  • Device: Laptop
  • Browser: FireFox
  • OS: Ubuntu 18.04
  • Provided sample code: Yes
  • Provided link: No