Search code examples
javascriptreactjsmui-datatable

MUI-Datatable, child component detecting change in the state of the parent component


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:

enter image description here

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?


Solution

  • 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}
    />