I'm starting with React and this problem has taken my sleep away.
From what I understand, the state (this.state) is only accessible in the component in which it was created, but in my project this is not happening. A child component is able to identify changes made to the state of the parent component, and this has slowed my application, let me try to explain it better with the code:
In the parent component, I render the page title, a modal for adding new records and include the child component responsible for assembling a datatable (provided by MUI-Datatable and data loaded via API):
https://github.com/gregnb/mui-datatables
Parent component:
import React from 'react';
import Typography from '@material-ui/core/Typography';
import DialogContent from '@material-ui/core/DialogContent';
import DialogTitle from '@material-ui/core/DialogTitle';
import Button from '@material-ui/core/Button';
// Components
import CadcliFieldContent from './CadcliFieldContent';
class CadcliField extends React.Component {
state = {
isLoading: false,
isNew: true,
dialog: false,
formData: {
name: 'Test'
}
};
openDialog = () => { this.setState({ dialog: true }); }
closeDialog = () => { this.setState({ dialog: false }); }
formDataChange = name => event => {
var formDataTemp = this.state.formData;
formDataTemp[name] = event.target.value;
this.setState({ formData: formDataTemp });
};
render () {
return (
<div className="flex flex-1 w-full">
<div className="flex items-center justify-end">
<Button
className="normal-case"
variant="contained"
color="secondary"
role="button"
onClick={this.openDialog}
>
<Icon>add</Icon>
<span className="mx-1">{t('NEW_FIELD')}</span>
</Button>
</div>
<CadcliFieldContent
urlBase="cadcli-field/"
/>
<Dialog
className='max-w-lg w-full m-24'
onClose={this.closeDialog}
open={this.state.dialog}
>
<DialogTitle component="div" className="p-0">
<Typography className="text-16 ml-8">Fields</Typography>
</DialogTitle>
<DialogContent className="p-16 sm:p-24">
<div className="flex items-center mb-24">
<TextField
type="text"
name="name"
id="input-name"
value={formData.name}
onChange={this.formDataChange('name')}
variant="outlined"
fullWidth
required
/>
</div>
</DialogContent>
</Dialog>
</div>
);
};
}
export default CadcliField;
Child component
import React from 'react';
import axios from 'axios';
import MUIDataTable from "mui-datatables";
import { CircularProgress, Typography } from '@material-ui/core';
class CadcliContent extends React.Component {
state = {
urlApi: process.env.REACT_APP_API_URL,
page: 0,
count: 1,
rowsPerPage: 10,
denseTable: true,
idDeleteModal: 0,
sortOrder: {},
filter: {},
search: '',
data: [['Loading...']],
columns: [
{name: 'id', label: this.props.t('utils:ACTIONS')},
{name: 'id', label: 'ID', options: { filterType : 'textField' }},
],
isLoading: false,
};
componentDidMount() {
this.getData(this.state.urlApi, 0);
}
// get data
getData = async (url, page) => {
this.setState({ isLoading: true });
const res = await this.axiosRequest(url, page);
this.setState({ data: res.data, isLoading: false, count: res.total });
};
axiosRequest = (url, page, sortOrder = {}, filter = {}, searchText = '') => {
url = url + '?page_size=' + this.state.rowsPerPage;
url += '&page=' + (page + 1);
// Ordering
if (sortOrder.name !== undefined) {
if (sortOrder.direction === 'asc') {
url += '&ordering=' + sortOrder.name;
} else {
url += '&ordering=-' + sortOrder.name;
}
}
// Filtering
if (filter.length > 0) {
var columnsTable = this.state.columns;
filter.forEach(function(value, key){
if (value.length > 0) {
url += '&' + columnsTable[key].name + '=' + value;
}
});
}
// Generic Searching
if (searchText !== "" && searchText !== null) {
url += '&search=' + searchText;
}
return new Promise((resolve, reject) => {
axios
.get(url)
.then(response => {
resolve({
data: response.data.results,
page: page,
total: response.data.count,
});
}).catch(function (error) {
resolve({
data: [],
page: 0,
total: 0,
});
});
});
};
sort = (page, sortOrder, filter) => {
this.setState({ isLoading: true });
this.axiosRequest(this.state.urlApi, page, sortOrder, filter).then(res => {
this.setState({
data: res.data,
page: res.page,
sortOrder,
filter,
isLoading: false,
count: res.total,
});
});
};
changePage = (page, sortOrder, filter) => {
this.setState({
isLoading: true,
});
this.axiosRequest(this.state.urlApi, page, sortOrder, filter).then(res => {
this.setState({
isLoading: false,
page: res.page,
sortOrder,
filter,
data: res.data,
count: res.total,
});
});
};
filterChange = (page, sortOrder, filter) => {
this.setState({
isLoading: true,
});
this.axiosRequest(this.state.urlApi, page, sortOrder, filter).then(res => {
this.setState({
isLoading: false,
page: res.page,
sortOrder,
filter,
data: res.data,
count: res.total,
});
});
};
searchTable = (page, sortOrder, columnsFilter, searchText) => {
this.setState({
isLoading: true,
});
this.axiosRequest(this.state.urlApi, page, sortOrder, columnsFilter, searchText).then(res => {
this.setState({
isLoading: false,
page: res.page,
sortOrder,
filter: columnsFilter,
search: searchText,
data: res.data,
count: res.total,
});
});
}
render() {
const { data, count, isLoading, rowsPerPage, sortOrder } = this.state;
const options = {
filter: true,
filterType: 'dropdown',
responsive: 'vertical',
serverSide: true,
download: false,
count: count,
rowsPerPage: rowsPerPage,
rowsPerPageOptions: [],
sortOrder: sortOrder,
resizableColumns: false,
confirmFilters: true,
onTableChange: (action, tableState) => {
switch (action) {
case 'changePage':
this.changePage(tableState.page, tableState.sortOrder, tableState.filterList);
break;
case 'sort':
this.sort(tableState.page, tableState.sortOrder, tableState.filterList);
break;
case 'search':
this.searchTable(tableState.page, tableState.sortOrder, tableState.filterList, tableState.searchText);
break;
default:
console.log('[' + action + '] action not handled.');
}
},
setTableProps: () => {
return {
size: this.state.denseTable ? 'small' : 'medium'
};
},
};
return (
<div className="w-full flex flex-col">
<MUIDataTable
title={
<Typography variant="h6">
Listing
{isLoading && <CircularProgress size={24} />}
</Typography>
}
data={data}
columns={this.state.columns}
options={options}
/>
</div>
);
}
}
export default CadcliContent;
What happens:
Every time I change the state in the parent component, for example, type in TextField and change the this.state.formData.name property, or click the button to open the modal (change this.state.dialog) the onTableChange function of the MUI Datatables is triggered and generates a processing in my table, I realize this because I put a console.log () to inform this:
onTableChange: (action, tableState) => {
switch (action) {
/* .... */
default:
console.log('[' + action + '] action not handled.');
}
},
(Excerpt from the code where console.log is triggered)
This disturbs me because MUI-Datatable understands that it has changed something in this.state and does a very heavy processing, even leaving the processing of typing in the delayed input.
Any idea what might be going on?
The solution was simpler than I imagined, and I ended up solving it by studying and understanding the React state concept.
Even though I still didn't understand how the MUI-Datables component observed the entire state, the simple fact of encapsulating the Dialog inside a new component, with a new isolated state, the magic happened.
So if someone goes through the same problem the solution was this:
CadcliField.js
<CadcliFieldContent
urlBase="cadcli-field/"
{...this.props}
/>
<CadcliFieldFormNew
isNew={this.state.isNew}
dialog={this.state.dialog}
closeDialog={this.closeDialog}
/>