Repository: gregnb/mui-datatables
Pull request #195 (2018-09-16 21:04:04)

Add a onTableChange callback to get table state upon any action
I'd like to be able to subscribe to the table state as a whole for any action regardless of the serverSide option, in order to treat the table as more of a controlled component.

Usecase: I want to allow a user to change the shape of the table and save the structure. I am using the table as form component of sorts.

I've started with piggybacking on the setServerRequest (renamed to setTableAction), and continued with selectRowUpdate, selectRowDelete and columnViewChange.

If this is helpful and desired, great. If you think there is a better route let me know :)

Cheers,
JP

coveralls: 
[![Coverage Status](https://coveralls.io/builds/19025947/badge)](https://coveralls.io/builds/19025947)

Coverage decreased (-0.1%) to 78.064% when pulling **f7074874868ae41754abdb24cb8982e434b0ad0a on APIOpoly:feature/onTableChange** into **c734dc277bafd071a82baf7173b77eadb23defca on gregnb:master**.

gregnb: Let me think about this for some time. I'm leaning towards having one property instead of two that do the same thing and make a breaking change considering the current version is still beta.


gregnb: @jpstrikesback Let's go ahead and update the README in the serverSide section to reference the `onTableChange` callback. Remove any mention of the `onServerSide` throughout the project and also update the example using it

I'm ready to make that breaking change and I think this is a great PR
jpstrikesback: Awesome, will do, thanks!!

Patch:
diff --git a/README.md b/README.md
index c30eb05..3aa8f8c 100644
--- a/README.md
+++ b/README.md
@@ -66,7 +66,7 @@ const columns = [
    filter: true,
    sort: true,
   }
- },      
+ },
  {
   name: "Company",
   options: {
@@ -80,7 +80,7 @@ const columns = [
    filter: true,
    sort: false,
   }
- },  
+ },
  {
   name: "State",
   options: {
@@ -163,7 +163,7 @@ The component accepts the following props:
 |**`onFilterChange`**|function||Callback function that triggers when filters have changed. `function(changedColumn: string, filterList: array) => void`
 |**`onColumnSortChange`**|function||Callback function that triggers when a column has been sorted. `function(changedColumn: string, direction: string) => void`
 |**`onColumnViewChange`**|function||Callback function that triggers when a column view has been changed. `function(changedColumn: string, action: string) => void`
-|**`onServerRequest`**|function||Callback function that triggers when the 'serverSide' option is enabled and table state has changed. `function(action: string, tableState: object) => void`
+|**`onTableChange`**|function||Callback function that triggers when table state has changed. `function(action: string, tableState: object) => void`
 
 
 ## Customize Columns
@@ -180,7 +180,7 @@ const columns = [
   }
  },
  ...
-];  
+];
 ```
 
 #### Column:
@@ -277,7 +277,7 @@ If you are looking to work with remote data sets or handle pagination, filtering
 ```
 const options = {
   serverSide: true,
-  onServerRequest: (action, tableState) => {
+  onTableChange: (action, tableState) => {
     this.xhrRequest('my.api.com/tableData', result => {
       this.setState({ data: result });
     });
diff --git a/examples/serverside-pagination/index.js b/examples/serverside-pagination/index.js
index 126712e..247e8d9 100644
--- a/examples/serverside-pagination/index.js
+++ b/examples/serverside-pagination/index.js
@@ -33,13 +33,13 @@ class Example extends React.Component {
         ["Aaren Rose", "Business Analyst", "Toledo"]
       ];
 
-      const maxRound =  Math.floor(Math.random() * 2) + 1;    
+      const maxRound =  Math.floor(Math.random() * 2) + 1;
       const data = [...Array(maxRound)].reduce(acc => acc.push(...srcData) && acc, []);
       data.sort((a, b) => 0.5 - Math.random());
 
       setTimeout(() => {
         resolve(data);
-      }, 250); 
+      }, 250);
 
     });
 
@@ -47,7 +47,7 @@ class Example extends React.Component {
 
   changePage = (page) => {
     this.xhrRequest(`/myApiServer?page=${page}`).then(data => {
-      this.setState({ 
+      this.setState({
         page: page,
         data
       });
@@ -66,10 +66,10 @@ class Example extends React.Component {
       serverSide: true,
       count: count,
       page: page,
-      onServerRequest: (action, tableState) => {
+      onTableChange: (action, tableState) => {
 
         console.log(action, tableState);
-        // a developer could react to change on an action basis or 
+        // a developer could react to change on an action basis or
         // examine the state as a whole and do whatever they want
 
         switch (action) {
diff --git a/src/MUIDataTable.js b/src/MUIDataTable.js
index df3f964..128c753 100644
--- a/src/MUIDataTable.js
+++ b/src/MUIDataTable.js
@@ -76,6 +76,7 @@ class MUIDataTable extends React.Component {
       resizableColumns: PropTypes.bool,
       selectableRows: PropTypes.bool,
       serverSide: PropTypes.bool,
+      onTableChange: PropTypes.func,
       caseSensitive: PropTypes.bool,
       rowHover: PropTypes.bool,
       page: PropTypes.number,
@@ -184,14 +185,14 @@ class MUIDataTable extends React.Component {
   }
 
   validateOptions(options) {
-    if (options.serverSide && options.onServerRequest === undefined) {
-      throw Error("onServerRequest callback must be provided when using serverSide option");
+    if (options.serverSide && options.onTableChange === undefined) {
+      throw Error("onTableChange callback must be provided when using serverSide option");
     }
   }
 
-  setServerRequest = action => {
-    if (this.options.serverSide) {
-      this.options.onServerRequest(action, this.state);
+  setTableAction = (action) => {
+    if (typeof this.options.onTableChange === "function") {
+      this.options.onTableChange(action, this.state);
     }
   };
 
@@ -216,7 +217,7 @@ class MUIDataTable extends React.Component {
    *  Build the source table data
    */
 
-  setTableData(props, status) {
+  setTableData(props, status, callback = () => {}) {
     const { data, columns, options } = props;
 
     let columnData = [],
@@ -303,14 +304,17 @@ class MUIDataTable extends React.Component {
     }
 
     /* set source data and display Data set source set */
-    this.setState(prevState => ({
-      columns: columnData,
-      filterData: filterData,
-      filterList: filterList,
-      selectedRows: selectedRowsData,
-      data: tableData,
-      displayData: this.getDisplayData(columnData, tableData, filterList, prevState.searchText),
-    }));
+    this.setState(
+      prevState => ({
+        columns: columnData,
+        filterData: filterData,
+        filterList: filterList,
+        selectedRows: selectedRowsData,
+        data: tableData,
+        displayData: this.getDisplayData(columnData, tableData, filterList, prevState.searchText),
+      }),
+      callback,
+    );
   }
 
   /*
@@ -447,6 +451,7 @@ class MUIDataTable extends React.Component {
         };
       },
       () => {
+        this.setTableAction("columnViewChange");
         if (this.options.onColumnViewChange) {
           this.options.onColumnViewChange(
             this.state.columns[index].name,
@@ -506,7 +511,7 @@ class MUIDataTable extends React.Component {
         return newState;
       },
       () => {
-        this.setServerRequest("sort");
+        this.setTableAction("sort");
         if (this.options.onColumnSortChange) {
           this.options.onColumnSortChange(
             this.state.columns[index].name,
@@ -523,7 +528,7 @@ class MUIDataTable extends React.Component {
         rowsPerPage: rows,
       }),
       () => {
-        this.setServerRequest("changeRowsPerPage");
+        this.setTableAction("changeRowsPerPage");
         if (this.options.onChangeRowsPerPage) {
           this.options.onChangeRowsPerPage(this.state.rowsPerPage);
         }
@@ -537,7 +542,7 @@ class MUIDataTable extends React.Component {
         page: page,
       }),
       () => {
-        this.setServerRequest("changePage");
+        this.setTableAction("changePage");
         if (this.options.onChangePage) {
           this.options.onChangePage(this.state.page);
         }
@@ -554,7 +559,7 @@ class MUIDataTable extends React.Component {
           : this.getDisplayData(prevState.columns, prevState.data, prevState.filterList, text),
       }),
       () => {
-        this.setServerRequest("search");
+        this.setTableAction("search");
       },
     );
   };
@@ -572,7 +577,7 @@ class MUIDataTable extends React.Component {
         };
       },
       () => {
-        this.setServerRequest("resetFilters");
+        this.setTableAction("resetFilters");
         if (this.options.onFilterChange) {
           this.options.onFilterChange(null, this.state.filterList);
         }
@@ -605,7 +610,7 @@ class MUIDataTable extends React.Component {
         };
       },
       () => {
-        this.setServerRequest("filterChange");
+        this.setTableAction("filterChange");
         if (this.options.onFilterChange) {
           this.options.onFilterChange(column, this.state.filterList);
         }
@@ -632,6 +637,9 @@ class MUIDataTable extends React.Component {
         },
       },
       TABLE_LOAD.UPDATE,
+      () => {
+        this.setTableAction("rowDelete");
+      },
     );
   };
 
@@ -672,6 +680,7 @@ class MUIDataTable extends React.Component {
           };
         },
         () => {
+          this.setTableAction("rowsSelect");
           if (this.options.onRowsSelect) {
             this.options.onRowsSelect(this.state.curSelectedRows, this.state.selectedRows.data);
           }
@@ -705,6 +714,7 @@ class MUIDataTable extends React.Component {
           };
         },
         () => {
+          this.setTableAction("rowsSelect");
           if (this.options.onRowsSelect) {
             this.options.onRowsSelect([value], this.state.selectedRows.data);
           }


Test patch:
diff --git a/test/MUIDataTable.test.js b/test/MUIDataTable.test.js
index c131aa0..132bbc2 100644
--- a/test/MUIDataTable.test.js
+++ b/test/MUIDataTable.test.js
@@ -1,5 +1,5 @@
 import React from "react";
-import { spy } from "sinon";
+import { spy, stub } from "sinon";
 import { mount, shallow } from "enzyme";
 import { assert, expect, should } from "chai";
 import MUIDataTable from "../src/MUIDataTable";
@@ -430,4 +430,35 @@ describe("<MUIDataTable />", function() {
     const state = shallowWrapper.state();
     assert.deepEqual(state.data[0].data[2], "Las Vegas");
   });
+  it("should call onTableChange when calling selectRowUpdate method with type=head", () => {
+    const options = { selectableRows: true, onTableChange: spy() };
+    const shallowWrapper = shallow(<MUIDataTable columns={columns} data={data} options={options} />).dive();
+    const instance = shallowWrapper.instance();
+
+    instance.selectRowUpdate("head", 0);
+    shallowWrapper.update();
+
+    const state = shallowWrapper.state();
+    const expectedResult = [
+      { index: 0, dataIndex: 0 },
+      { index: 1, dataIndex: 1 },
+      { index: 2, dataIndex: 2 },
+      { index: 3, dataIndex: 3 },
+    ];
+    assert.deepEqual(state.selectedRows.data, expectedResult);
+    assert.strictEqual(options.onTableChange.callCount, 1);
+  });
+  it("should call onTableChange when calling selectRowUpdate method with type=cell", () => {
+    const options = { selectableRows: true, onTableChange: spy() };
+
+    const shallowWrapper = shallow(<MUIDataTable columns={columns} data={data} options={options} />).dive();
+    const instance = shallowWrapper.instance();
+
+    instance.selectRowUpdate("cell", 0);
+    shallowWrapper.update();
+
+    const state = shallowWrapper.state();
+    assert.deepEqual(state.selectedRows.data, [0]);
+    assert.strictEqual(options.onTableChange.callCount, 1);
+  });
 });