Repository: gregnb/mui-datatables
Pull request #560 (2019-04-12 03:35:17)

Fix for Issue 483
Fixes issue https://github.com/gregnb/mui-datatables/issues/483 (`changedColumn` was showing filtered value instead of column name).

When removing filters (`TableFilterList.js`), there is no information about column names, so `null` is supplied instead. We could default to the filter value removed in this case (as it was previously), but not sure what is best.

UPDATE:

The proper column name should now always be passed along with the filter information. This was done by passing the column names along to the filter list component, and correlating them with filter position (which matches column position).

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

Coverage decreased (-0.4%) to 71.509% when pulling **8760dfc0793f4b21e96c766a0df4e6a5ac4bad50 on gabrielliwerant:issue-483** into **247b6703015edb404730ef39a9c83a760a3225b8 on gregnb:master**.

gregnb: Alright let me think on this one maybe we can come up with a better solution to solve that null issue
gabrielliwerant: I added a state value to pass into the `TableFilterList` in order to prevent the `null` after removing filters. Also added testing around the filtered column name in the callback in order to help prevent regressions. @gregnb 

Patch:
diff --git a/examples/column-filters/index.js b/examples/column-filters/index.js
index e6368b2..02c9d5b 100644
--- a/examples/column-filters/index.js
+++ b/examples/column-filters/index.js
@@ -15,11 +15,12 @@ class Example extends React.Component {
           customFilterListRender: v => `Name: ${v}`,
           filterOptions: ['a', 'b', 'c', 'Business Analyst']
         }
-      },      
+      },
       {
         name: "Title",
         options: {
           filter: true,
+          filterList: ['Business Analyst'],
           customFilterListRender: v => `Title: ${v}`,
           filterType: 'textField' // set filterType's at the column level
         }
@@ -44,7 +45,7 @@ class Example extends React.Component {
           customFilterListRender: v => `Salary: ${v}`,
           sort: false
         }
-      }      
+      }
     ];
     const data = [
       ["Gabby George", "Business Analyst", "Minneapolis", 30, 100000],
@@ -81,6 +82,9 @@ class Example extends React.Component {
 
     const options = {
       filter: true,
+      onFilterChange: (changedColumn, filterList) => {
+        console.log(changedColumn, filterList);
+      },
       selectableRows: true,
       filterType: 'dropdown',
       responsive: 'stacked',
diff --git a/examples/component/index.js b/examples/component/index.js
index 0098f1f..82f155a 100644
--- a/examples/component/index.js
+++ b/examples/component/index.js
@@ -1,6 +1,7 @@
 import React from "react";
 import ReactDOM from "react-dom";
 import FormControlLabel from '@material-ui/core/FormControlLabel';
+import TextField from '@material-ui/core/TextField';
 import Switch from '@material-ui/core/Switch';
 import MUIDataTable from "../../src/";
 import Cities from "./cities";
@@ -14,8 +15,15 @@ class Example extends React.Component {
         name: "Name",
         options: {
           filter: false,
+          customBodyRender: (value, tableMeta, updateValue) => (
+            <FormControlLabel
+              value={value}
+              control={<TextField value={value} />}
+              onChange={event => updateValue(event.target.value)}
+            />
+          )
         }
-      },      
+      },
       {
         name: "Title",
         options: {
@@ -34,14 +42,19 @@ class Example extends React.Component {
                 change={event => updateValue(event)}
               />
             );
-          },         
+          },
         }
       },
       {
         name: "Age",
         options: {
           filter: false,
-          customBodyRender: value => value
+          customBodyRender: (value, tableMeta, updateValue) => (
+            <FormControlLabel
+              control={<TextField value={value || ''} type='number' />}
+              onChange={event => updateValue(event.target.value)}
+            />
+          )
         }
       },
       {
diff --git a/src/MUIDataTable.js b/src/MUIDataTable.js
index 5b45796..d02112f 100644
--- a/src/MUIDataTable.js
+++ b/src/MUIDataTable.js
@@ -792,21 +792,21 @@ class MUIDataTable extends React.Component {
     );
   };
 
-  filterUpdate = (index, column, type) => {
+  filterUpdate = (index, value, column, type) => {
     this.setState(
       prevState => {
         const filterList = cloneDeep(prevState.filterList);
-        const filterPos = filterList[index].indexOf(column);
+        const filterPos = filterList[index].indexOf(value);
 
         switch (type) {
           case 'checkbox':
-            filterPos >= 0 ? filterList[index].splice(filterPos, 1) : filterList[index].push(column);
+            filterPos >= 0 ? filterList[index].splice(filterPos, 1) : filterList[index].push(value);
             break;
           case 'multiselect':
-            filterList[index] = column === '' ? [] : column;
+            filterList[index] = value === '' ? [] : value;
             break;
           default:
-            filterList[index] = filterPos >= 0 || column === '' ? [] : [column];
+            filterList[index] = filterPos >= 0 || value === '' ? [] : [value];
         }
 
         return {
@@ -1045,6 +1045,7 @@ class MUIDataTable extends React.Component {
     const rowCount = this.options.count || displayData.length;
     const rowsPerPage = this.options.pagination ? this.state.rowsPerPage : displayData.length;
     const showToolbar = hasToolbarItem(this.options, title);
+    const columnNames = columns.map(column => ({ name: column.name }));
 
     return (
       <Paper
@@ -1085,6 +1086,7 @@ class MUIDataTable extends React.Component {
           })}
           filterList={filterList}
           filterUpdate={this.filterUpdate}
+          columnNames={columnNames}
         />
         <div
           style={{ position: 'relative' }}
diff --git a/src/components/TableFilter.js b/src/components/TableFilter.js
index ea975fa..758ffde 100644
--- a/src/components/TableFilter.js
+++ b/src/components/TableFilter.js
@@ -125,22 +125,22 @@ class TableFilter extends React.Component {
     classes: PropTypes.object,
   };
 
-  handleCheckboxChange = (index, column) => {
-    this.props.onFilterUpdate(index, column, 'checkbox');
+  handleCheckboxChange = (index, value, column) => {
+    this.props.onFilterUpdate(index, value, column, 'checkbox');
   };
 
-  handleDropdownChange = (event, index) => {
+  handleDropdownChange = (event, index, column) => {
     const labelFilterAll = this.props.options.textLabels.filter.all;
     const value = event.target.value === labelFilterAll ? '' : event.target.value;
-    this.props.onFilterUpdate(index, value, 'dropdown');
+    this.props.onFilterUpdate(index, value, column, 'dropdown');
   };
 
-  handleMultiselectChange = (index, column) => {
-    this.props.onFilterUpdate(index, column, 'multiselect');
+  handleMultiselectChange = (index, value, column) => {
+    this.props.onFilterUpdate(index, value, column, 'multiselect');
   };
 
-  handleTextFieldChange = (event, index) => {
-    this.props.onFilterUpdate(index, event.target.value, 'textField');
+  handleTextFieldChange = (event, index, column) => {
+    this.props.onFilterUpdate(index, event.target.value, column, 'textField');
   };
 
   renderCheckbox(column, index) {
@@ -155,7 +155,7 @@ class TableFilter extends React.Component {
             </Typography>
           </Grid>
           <Grid container>
-            {filterData[index].map((filterColumn, filterIndex) => (
+            {filterData[index].map((filterValue, filterIndex) => (
               <Grid item key={filterIndex}>
                 <FormControlLabel
                   key={filterIndex}
@@ -166,16 +166,16 @@ class TableFilter extends React.Component {
                   control={
                     <Checkbox
                       className={classes.checkboxIcon}
-                      onChange={this.handleCheckboxChange.bind(null, index, filterColumn)}
-                      checked={filterList[index].indexOf(filterColumn) >= 0 ? true : false}
+                      onChange={this.handleCheckboxChange.bind(null, index, filterValue, column.name)}
+                      checked={filterList[index].indexOf(filterValue) >= 0 ? true : false}
                       classes={{
                         root: classes.checkbox,
                         checked: classes.checked,
                       }}
-                      value={filterColumn != null ? filterColumn.toString() : ''}
+                      value={filterValue != null ? filterValue.toString() : ''}
                     />
                   }
-                  label={filterColumn}
+                  label={filterValue}
                 />
               </Grid>
             ))}
@@ -197,14 +197,14 @@ class TableFilter extends React.Component {
             <Select
               value={filterList[index].toString() || textLabels.all}
               name={column.name}
-              onChange={event => this.handleDropdownChange(event, index)}
+              onChange={event => this.handleDropdownChange(event, index, column.name)}
               input={<Input name={column.name} id={column.name} />}>
               <MenuItem value={textLabels.all} key={0}>
                 {textLabels.all}
               </MenuItem>
-              {filterData[index].map((filterColumn, filterIndex) => (
-                <MenuItem value={filterColumn} key={filterIndex + 1}>
-                  {filterColumn != null ? filterColumn.toString() : ''}
+              {filterData[index].map((filterValue, filterIndex) => (
+                <MenuItem value={filterValue} key={filterIndex + 1}>
+                  {filterValue != null ? filterValue.toString() : ''}
                 </MenuItem>
               ))}
             </Select>
@@ -224,7 +224,7 @@ class TableFilter extends React.Component {
             <TextField
               label={column.label}
               value={filterList[index].toString() || ''}
-              onChange={event => this.handleTextFieldChange(event, index)}
+              onChange={event => this.handleTextFieldChange(event, index, column.name)}
             />
           </FormControl>
         </div>
@@ -245,20 +245,20 @@ class TableFilter extends React.Component {
               value={filterList[index] || []}
               renderValue={selected => selected.join(', ')}
               name={column.name}
-              onChange={event => this.handleMultiselectChange(index, event.target.value)}
+              onChange={event => this.handleMultiselectChange(index, event.target.value, column.name)}
               input={<Input name={column.name} id={column.name} />}>
-              {filterData[index].map((filterColumn, filterIndex) => (
-                <MenuItem value={filterColumn} key={filterIndex + 1}>
+              {filterData[index].map((filterValue, filterIndex) => (
+                <MenuItem value={filterValue} key={filterIndex + 1}>
                   <Checkbox
-                    checked={filterList[index].indexOf(filterColumn) >= 0 ? true : false}
-                    value={filterColumn.toString()}
+                    checked={filterList[index].indexOf(filterValue) >= 0 ? true : false}
+                    value={filterValue.toString()}
                     className={classes.checkboxIcon}
                     classes={{
                       root: classes.checkbox,
                       checked: classes.checked,
                     }}
                   />
-                  <ListItemText primary={filterColumn} />
+                  <ListItemText primary={filterValue} />
                 </MenuItem>
               ))}
             </Select>
diff --git a/src/components/TableFilterList.js b/src/components/TableFilterList.js
index 9729f05..2f4ece6 100644
--- a/src/components/TableFilterList.js
+++ b/src/components/TableFilterList.js
@@ -21,6 +21,13 @@ class TableFilterList extends React.Component {
     filterList: PropTypes.array.isRequired,
     /** Filter List value renderers */
     filterListRenderers: PropTypes.array.isRequired,
+    /** Columns used to describe table */
+    columnNames: PropTypes.PropTypes.arrayOf(
+      PropTypes.oneOfType([
+        PropTypes.string,
+        PropTypes.shape({ name: PropTypes.string.isRequired }),
+      ]),
+    ).isRequired,
     /** Callback to trigger filter update */
     onFilterUpdate: PropTypes.func,
     /** Extend the style applied to components */
@@ -28,7 +35,7 @@ class TableFilterList extends React.Component {
   };
 
   render() {
-    const { classes, filterList, filterUpdate, filterListRenderers } = this.props;
+    const { classes, filterList, filterUpdate, filterListRenderers, columnNames } = this.props;
 
     return (
       <div className={classes.root}>
@@ -37,7 +44,7 @@ class TableFilterList extends React.Component {
             <Chip
               label={filterListRenderers[index](data)}
               key={colIndex}
-              onDelete={filterUpdate.bind(null, index, data, 'checkbox')}
+              onDelete={filterUpdate.bind(null, index, data, columnNames[index].name, 'checkbox')}
               className={classes.chip}
             />
           )),
diff --git a/webpack.config.js b/webpack.config.js
index f3cd389..6a2037e 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -3,7 +3,7 @@ const webpack = require('webpack');
 
 module.exports = {
   entry: {
-    app: "./examples/selectable-rows/index.js"
+    app: "./examples/column-filters/index.js"
   },
   stats: "verbose",
   context: __dirname,


Test patch:
diff --git a/test/MUIDataTable.test.js b/test/MUIDataTable.test.js
index 640b624..32bf2c5 100644
--- a/test/MUIDataTable.test.js
+++ b/test/MUIDataTable.test.js
@@ -369,7 +369,7 @@ describe('<MUIDataTable />', function() {
     const shallowWrapper = shallow(<MUIDataTable columns={columns} data={data} />);
     const table = shallowWrapper.dive();
     const instance = table.instance();
-    instance.filterUpdate(0, 'Joe James', 'checkbox');
+    instance.filterUpdate(0, 'Joe James', 'Name', 'checkbox');
     table.update();
 
     const state = table.state();
@@ -380,7 +380,7 @@ describe('<MUIDataTable />', function() {
     const shallowWrapper = shallow(<MUIDataTable columns={columns} data={data} />);
     const table = shallowWrapper.dive();
     const instance = table.instance();
-    instance.filterUpdate(0, 'Joe James', 'checkbox');
+    instance.filterUpdate(0, 'Joe James', 'Name', 'checkbox');
     table.update();
 
     let state = table.state();
@@ -397,7 +397,7 @@ describe('<MUIDataTable />', function() {
     const shallowWrapper = shallow(<MUIDataTable columns={columns} data={data} />);
     const table = shallowWrapper.dive();
     const instance = table.instance();
-    instance.filterUpdate(0, 'Joe James', 'dropdown');
+    instance.filterUpdate(0, 'Joe James', 'Name', 'dropdown');
     table.update();
 
     const state = table.state();
@@ -413,9 +413,10 @@ describe('<MUIDataTable />', function() {
       defaultRenderCustomFilterList,
       defaultRenderCustomFilterList,
     ];
+    const columnNames = columns.map(column => ({ name: column.name }));
 
     const mountWrapper = mount(
-      <TableFilterList filterList={filterList} filterListRenderers={filterListRenderers} filterUpdate={() => true} />,
+      <TableFilterList filterList={filterList} filterListRenderers={filterListRenderers} filterUpdate={() => true} columnNames={columnNames} />,
     );
     const actualResult = mountWrapper.find(Chip);
     assert.strictEqual(actualResult.length, 1);
@@ -428,9 +429,10 @@ describe('<MUIDataTable />', function() {
         ? c.options.customFilterListRender
         : defaultRenderCustomFilterList;
     });
+    const columnNames = columns.map(column => ({ name: column.name }));
 
     const mountWrapper = mount(
-      <TableFilterList filterList={filterList} filterListRenderers={filterListRenderers} filterUpdate={() => true} />,
+      <TableFilterList filterList={filterList} filterListRenderers={filterListRenderers} filterUpdate={() => true} columnNames={columnNames} />,
     );
     const actualResult = mountWrapper.find(Chip);
     assert.strictEqual(actualResult.length, 1);
@@ -441,13 +443,13 @@ describe('<MUIDataTable />', function() {
     const shallowWrapper = shallow(<MUIDataTable columns={columns} data={data} />);
     const table = shallowWrapper.dive();
     const instance = table.instance();
-    instance.filterUpdate(0, 'Joe James', 'dropdown');
+    instance.filterUpdate(0, 'Joe James', 'Name', 'dropdown');
     table.update();
 
     let state = table.state();
     assert.deepEqual(state.filterList, [['Joe James'], [], [], [], []]);
 
-    instance.filterUpdate(0, 'Joe James', 'dropdown');
+    instance.filterUpdate(0, 'Joe James', 'Name', 'dropdown');
     table.update();
 
     state = table.state();
@@ -459,7 +461,7 @@ describe('<MUIDataTable />', function() {
     const shallowWrapper = shallow(<MUIDataTable columns={columns} data={data} />);
     const table = shallowWrapper.dive();
     const instance = table.instance();
-    instance.filterUpdate(0, 'Joe James', 'checkbox');
+    instance.filterUpdate(0, 'Joe James', 'Name', 'checkbox');
     table.update();
 
     // now remove it
@@ -472,6 +474,42 @@ describe('<MUIDataTable />', function() {
     assert.deepEqual(state.filterList, [[], [], [], [], []]);
   });
 
+  it('should have the proper column name in onFilterChange when applying filters', () => {
+    let changedColumn;
+    const options = {
+      onFilterChange: column => changedColumn = column
+    };
+
+    const shallowWrapper = shallow(<MUIDataTable columns={columns} data={data} options={options} />);
+    const table = shallowWrapper.dive();
+    const instance = table.instance();
+
+    // test checkbox filter
+    instance.filterUpdate(0, 'Joe James', 'Name', 'checkbox');
+    table.update();
+    assert.strictEqual(changedColumn, 'Name');
+
+    // test dropdown filter
+    instance.filterUpdate(0, 'Test Corp', 'Company', 'dropdown');
+    table.update();
+    assert.strictEqual(changedColumn, 'Company');
+
+    // test textField filter
+    instance.filterUpdate(0, 'Joe James', 'Name', 'textField');
+    table.update();
+    assert.strictEqual(changedColumn, 'Name');
+
+    // test multiselect filter
+    instance.filterUpdate(0, 'Test Corp', 'Company', 'multiselect');
+    table.update();
+    assert.strictEqual(changedColumn, 'Company');
+
+    // test reset filters
+    instance.resetFilters();
+    table.update();
+    assert.strictEqual(changedColumn, null);
+  });
+
   it('should properly set searchText when calling searchTextUpdate method', () => {
     const shallowWrapper = shallow(<MUIDataTable columns={columns} data={data} />);
     const table = shallowWrapper.dive();
@@ -814,7 +852,7 @@ describe('<MUIDataTable />', function() {
     const shallowWrapper = shallow(<MUIDataTable columns={columns} data={data} />);
     const table = shallowWrapper.dive();
     const instance = table.instance();
-    instance.filterUpdate(0, 'James, Joe', 'checkbox');
+    instance.filterUpdate(0, 'James, Joe', 'Name', 'checkbox');
     table.update();
     const state = table.state();
 
@@ -831,7 +869,7 @@ describe('<MUIDataTable />', function() {
     const shallowWrapper = shallow(<MUIDataTable columns={columns} data={data} options={options} />);
     const table = shallowWrapper.dive();
     const instance = table.instance();
-    instance.filterUpdate(0, 'James', 'textField');
+    instance.filterUpdate(0, 'James', 'Name', 'textField');
     table.update();
     const state = table.state();
 
@@ -848,7 +886,7 @@ describe('<MUIDataTable />', function() {
     const shallowWrapper = shallow(<MUIDataTable columns={columns} data={data} options={options} />);
     const table = shallowWrapper.dive();
     const instance = table.instance();
-    instance.filterUpdate(0, 'Joe James', 'dropdown');
+    instance.filterUpdate(0, 'Joe James', 'Name', 'dropdown');
     table.update();
     const state = table.state();
 
@@ -886,7 +924,7 @@ describe('<MUIDataTable />', function() {
       const shallowWrapper = shallow(<MUIDataTable columns={columns} data={data} options={options} />);
       const table = shallowWrapper.dive();
       const instance = table.instance();
-      instance.filterUpdate(1, 'b', 'dropdown');
+      instance.filterUpdate(1, 'b', 'Name', 'dropdown');
       table.update();
       const { displayData } = table.state();
 
@@ -934,7 +972,7 @@ describe('<MUIDataTable />', function() {
       const shallowWrapper = shallow(<MUIDataTable columns={columns} data={data} options={options} />);
       const table = shallowWrapper.dive();
       const instance = table.instance();
-      instance.filterUpdate(1, 'b', 'dropdown');
+      instance.filterUpdate(1, 'b', 'Name', 'dropdown');
       table.update();
       const { displayData } = table.state();