mui-datatables: Reactive data causing table state to be lost

Currently using Meteor to generate reactive data, and if I change any table settings (filter, sort, etc), they are lost if any of the data is updated. Previously, I thought it was because there were functions in options and columns that were updating the props, but it seems even the data prop, if updated, causes the state to be lost (for me). A sample of my code:

import { LabClinics } from "/imports/api/labclinics/labclinics";
import ReactSelect from "/imports/ui/Components/ReactSelect.js";
import {
    Button,
    Grid,
    IconButton,
    Paper,
    TextField,
    Toolbar,
    withStyles,
} from "@material-ui/core";
import CheckIcon from "@material-ui/icons/Check";
import { Meteor } from "meteor/meteor";
import { withTracker } from "meteor/react-meteor-data";
import MUIDataTable from "mui-datatables";
import React from "react";
import { toast } from "react-toastify";

class UsersComponent extends React.PureComponent {
    constructor(props) {
        super(props);
        this.state = {
            data: [],
            users: null,
            options: this.returnOptions(),
            columns: this.returnColumns(),
            clinic: null,
        };
        this.onSubmit = this.onSubmit.bind(this);
    }

    componentDidUpdate(prevProps, prevState, snapshot) {
        if (this.props.isReady && this.props !== prevProps) {
            this.createRows();
            this.createClinicSelectOptions();
        }
    }
    onChange(e) {
        console.log(e.target.value);
        this.setState({ [e.target.name]: e.target.value });
    }

    createClinicSelectOptions = () => {
        const clinicOptions = this.props.clinics.map((clinic) => ({
            label: clinic.AurusUser,
            value: clinic,
        }));
        this.setState({ clinicOptions });
    };

    onChangeClinic = (value) => {
        this.setState({ clinic: value });
    };
    
    returnOptions = () => ({
        onRowsSelect: (currentRowsSelected, allRowsSelected) => {
            if (currentRowsSelected.length <= 0) {
                this.setState({ clinic: null, users: null });
            } else {
                const users = [];
                currentRowsSelected.forEach((row) => {
                    users.push(this.state.data[row.dataIndex][0]);
                });
                this.setState({ users });
            }
        },
        customToolbarSelect: (selectedRows, displayData, setSelectedRows) => {
            return (
                <Toolbar
                    style={{ flexGrow: 0.1 }}
                    className={"toolbar-spacing"}
                >
                    {/*<Typography>Reassign User to Clinic:</Typography>*/}
                    {this.state.clinic && (
                        <React.Fragment>
                            <IconButton onClick={this.onSubmit}>
                                <CheckIcon fontSize={"small"} />
                            </IconButton>
                        </React.Fragment>
                    )}
                    <ReactSelect
                        options={this.state.clinicOptions}
                        placeholder={"Reassign User to Clinic"}
                        // label={"Reassign User to Clinic"}
                        helperText={"Reassign User to Clinic"}
                        onChange={this.onChangeClinic}
                    />
                </Toolbar>
            );
        },
        elevation: 1,
    });

    createRows = () => {
        const data = this.props.users.map((user) => [
            user,
            user.profile.username,
            user.profile.clinicId,
        ]);
        this.setState({ data });
    };

    returnColumns = () => [
        {
            name: "User",
            options: {
                display: "excluded",
            },
        },
        {
            name: "Username",
        },
        {
            name: "Clinic ID",
        },
    ];

    render() {
        const { classes } = this.props;
        const { data, options, columns } = this.state;
        return (            
            <MUIDataTable
                title={"Users"}
                data={data}
                options={options}
                columns={columns}
            />            
        );
    }
}

const styles = (theme) => ({
    iconButton: {},
    iconContainer: {
        marginRight: "24px",
    },
    inverseIcon: {
        transform: "rotate(90deg)",
    },
    textField: {
        marginLeft: theme.spacing(1),
        marginRight: theme.spacing(1),
    },
    paper: {
        ...theme.mixins.gutters(),
        paddingTop: theme.spacing(2),
        paddingBottom: theme.spacing(2),
    },
});

export default withTracker((props) => {
    const handles = [
        Meteor.subscribe("labClinics"),
        Meteor.subscribe("userList"),
    ];
    const isReady = handles.every((e) => e.ready());
    let clinics, users;
    if (isReady) {
        clinics = LabClinics.find().fetch();
        users = Meteor.users.find().fetch();
        debugger;
    }
    return {
        isReady,
        clinics,
        users,
    };
})(withStyles(styles)(UsersComponent));

Expected Behavior

Table state should be retained (filters, search text, etc) should any prop update on the MUIDataTable.

Current Behavior

All state resets if any changes in props are made (denoted by “propsChange” in onTableChange).

Steps to Reproduce (for bugs)

  1. Created new Meteor project and populated with test data.
  2. Fed data into datatable.
  3. De-selected a column from “View Columns”.
  4. Opened database editor and changed a value in the database to see if reflected reactively in table.
  5. Witnessed the de-selected column then become visible again on data change.

Your Environment

Tech Version
Material-UI 4.1.3
MUI-datatables 2.5.1 (but tested it on 2.4.0, 2.3.0, and 2.2.0
React 16.8.6
browser Chrome Version 75.0.3770.100 (Official Build) (64-bit)
OS MacOS 10.14.5

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Comments: 23

Most upvoted comments

I can validate this Bug, A propsUpdate event is triggering and resetting table state.

State in table is persistent as of version 2.15.0.

It’d be great if there was a way to serialize the whole state and pass it as modified object. A example on saving the whole state and passing it would be greatly appreciated.