• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

gregnb / mui-datatables / #1864

23 Jun 2026 05:20PM UTC coverage: 73.248% (+0.7%) from 72.5%
#1864

push

265 of 396 branches covered (66.92%)

Branch coverage included in aggregate %.

425 of 546 relevant lines covered (77.84%)

41.37 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

84.0
/src/components/TableFilter.js
1
import Button from '@mui/material/Button';
2
import Checkbox from '@mui/material/Checkbox';
3
import FormControl from '@mui/material/FormControl';
4
import FormControlLabel from '@mui/material/FormControlLabel';
5
import FormGroup from '@mui/material/FormGroup';
6
import Grid from '@mui/material/Grid';
7
import Input from '@mui/material/Input';
8
import InputLabel from '@mui/material/InputLabel';
9
import ListItemText from '@mui/material/ListItemText';
10
import MenuItem from '@mui/material/MenuItem';
11
import PropTypes from 'prop-types';
12
import React from 'react';
13
import Select from '@mui/material/Select';
14
import TextField from '@mui/material/TextField';
15
import Typography from '@mui/material/Typography';
16
import clsx from 'clsx';
1✔
17
import { withStyles } from 'tss-react/mui';
18
import cloneDeep from 'lodash.clonedeep';
19

20
export const defaultFilterStyles = theme => ({
21
  root: {
22
    backgroundColor: theme.palette.background.default,
23
    padding: '24px 24px 36px 24px',
24
    fontFamily: 'Roboto',
25
  },
26
  header: {
27
    flex: '0 0 auto',
28
    marginBottom: '16px',
29
    width: '100%',
30
    display: 'flex',
31
    justifyContent: 'space-between',
32
  },
33
  title: {
34
    display: 'inline-block',
35
    marginLeft: '7px',
36
    color: theme.palette.text.primary,
37
    fontSize: '14px',
38
    fontWeight: 500,
39
  },
40
  noMargin: {
41
    marginLeft: '0px',
42
  },
43
  reset: {
44
    alignSelf: 'left',
45
  },
46
  resetLink: {
47
    marginLeft: '16px',
48
    fontSize: '12px',
49
    cursor: 'pointer',
50
  },
51
  filtersSelected: {
52
    alignSelf: 'right',
53
  },
54
  /* checkbox */
55
  checkboxListTitle: {
56
    marginLeft: '7px',
57
    marginBottom: '8px',
58
    fontSize: '14px',
59
    color: theme.palette.text.secondary,
60
    textAlign: 'left',
61
    fontWeight: 500,
62
  },
63
  checkboxFormGroup: {
64
    marginTop: '8px',
65
  },
66
  checkboxFormControl: {
67
    margin: '0px',
68
  },
69
  checkboxFormControlLabel: {
70
    fontSize: '15px',
71
    marginLeft: '8px',
72
    color: theme.palette.text.primary,
73
  },
74
  checkboxIcon: {
75
    width: '32px',
76
    height: '32px',
77
  },
78
  checkbox: {},
79
  checked: {},
80
  gridListTile: {
81
    marginTop: '16px',
82
  },
83
});
84

85
class TableFilter extends React.Component {
86
  static propTypes = {
87
    /** Data used to populate filter dropdown/checkbox */
88
    filterData: PropTypes.array.isRequired,
89
    /** Data selected to be filtered against dropdown/checkbox */
90
    filterList: PropTypes.array.isRequired,
91
    /** Options used to describe table */
92
    options: PropTypes.object.isRequired,
93
    /** Callback to trigger filter update */
94
    onFilterUpdate: PropTypes.func,
95
    /** Callback to trigger filter reset */
96
    onFilterReset: PropTypes.func,
97
    /** Extend the style applied to components */
98
    classes: PropTypes.object,
99
  };
100

101
  constructor(props) {
102
    super(props);
103
    this.state = {
104
      filterList: cloneDeep(props.filterList),
105
    };
106
  }
107

108
  filterUpdate = (index, value, column, type, customUpdate) => {
109
    let newFilterList = this.state.filterList.slice(0);
110

111
    this.props.updateFilterByType(newFilterList, index, value, type, customUpdate);
112
    this.setState({
113
      filterList: newFilterList,
114
    });
115
  };
116

117
  handleCheckboxChange = (index, value, column) => {
118
    this.filterUpdate(index, value, column, 'checkbox');
119

120
    if (this.props.options.confirmFilters !== true) {
121
      this.props.onFilterUpdate(index, value, column, 'checkbox');
122
    }
123
  };
124

125
  handleDropdownChange = (event, index, column) => {
126
    const labelFilterAll = this.props.options.textLabels.filter.all;
1✔
127
    const value = event.target.value === labelFilterAll ? [] : [event.target.value];
128
    this.filterUpdate(index, value, column, 'dropdown');
129

130
    if (this.props.options.confirmFilters !== true) {
2✔
131
      this.props.onFilterUpdate(index, value, column, 'dropdown');
2✔
132
    }
133
  };
134

135
  handleMultiselectChange = (index, value, column) => {
×
136
    this.filterUpdate(index, value, column, 'multiselect');
137

138
    if (this.props.options.confirmFilters !== true) {
139
      this.props.onFilterUpdate(index, value, column, 'multiselect');
3✔
140
    }
141
  };
3✔
142

12✔
143
  handleTextFieldChange = (event, index, column) => {
144
    this.filterUpdate(index, event.target.value, column, 'textField');
145

146
    if (this.props.options.confirmFilters !== true) {
147
      this.props.onFilterUpdate(index, event.target.value, column, 'textField');
148
    }
149
  };
26✔
150

151
  handleCustomChange = (value, index, column) => {
152
    this.filterUpdate(index, value, column.name, column.filterType);
153

154
    if (this.props.options.confirmFilters !== true) {
155
      this.props.onFilterUpdate(index, value, column.name, column.filterType);
156
    }
157
  };
158

159
  renderCheckbox(column, index, components = {}) {
26!
160
    const CheckboxComponent = components.Checkbox || Checkbox;
161

162
    const { classes, filterData } = this.props;
163
    const { filterList } = this.state;
164
    const renderItem =
26!
165
      column.filterOptions && column.filterOptions.renderValue ? column.filterOptions.renderValue : v => v;
166

167
    return (
168
      <Grid item key={index} xs={6}>
169
        <FormGroup>
170
          <Grid item xs={12}>
171
            <Typography variant="body2" className={classes.checkboxListTitle}>
172
              {column.label}
173
            </Typography>
174
          </Grid>
175
          <Grid container>
176
            {filterData[index].map((filterValue, filterIndex) => (
177
              <Grid item key={filterIndex}>
178
                <FormControlLabel
179
                  key={filterIndex}
3✔
180
                  classes={{
3✔
181
                    root: classes.checkboxFormControl,
182
                    label: classes.checkboxFormControlLabel,
3✔
183
                  }}
184
                  control={
185
                    <CheckboxComponent
12✔
186
                      data-description="table-filter"
187
                      color="primary"
188
                      className={classes.checkboxIcon}
189
                      onChange={this.handleCheckboxChange.bind(null, index, filterValue, column.name)}
15✔
190
                      checked={filterList[index].indexOf(filterValue) >= 0}
191
                      classes={{
×
192
                        root: classes.checkbox,
193
                        checked: classes.checked,
194
                      }}
195
                      value={filterValue != null ? filterValue.toString() : ''}
196
                    />
197
                  }
26✔
198
                  label={renderItem(filterValue)}
26!
199
                />
200
              </Grid>
201
            ))}
202
          </Grid>
203
        </FormGroup>
204
      </Grid>
205
    );
206
  }
207

208
  renderSelect(column, index) {
209
    const { classes, filterData, options } = this.props;
210
    const { filterList } = this.state;
211
    const textLabels = options.textLabels.filter;
212
    const renderItem =
1✔
213
      column.filterOptions && column.filterOptions.renderValue
214
        ? column.filterOptions.renderValue
1✔
215
        : v => (v != null ? v.toString() : '');
216
    const width = (column.filterOptions && column.filterOptions.fullWidth) === true ? 12 : 6;
217

4!
218
    return (
219
      <Grid
220
        item
221
        key={index}
222
        xs={width}
4!
223
        classes={{ 'grid-xs-12': classes.gridListTile, 'grid-xs-6': classes.gridListTile }}>
1✔
224
        <FormControl key={index} variant={'standard'} fullWidth>
225
          <InputLabel htmlFor={column.name}>{column.label}</InputLabel>
×
226
          <Select
227
            fullWidth
228
            value={filterList[index].length ? filterList[index].toString() : textLabels.all}
13✔
229
            name={column.name}
230
            onChange={event => this.handleDropdownChange(event, index, column.name)}
13✔
231
            input={<Input name={column.name} id={column.name} />}>
232
            <MenuItem value={textLabels.all} key={0}>
233
              {textLabels.all}
234
            </MenuItem>
235
            {filterData[index].map((filterValue, filterIndex) => (
236
              <MenuItem value={filterValue} key={filterIndex + 1}>
237
                {renderItem(filterValue)}
238
              </MenuItem>
239
            ))}
240
          </Select>
241
        </FormControl>
242
      </Grid>
243
    );
244
  }
245

246
  renderTextField(column, index) {
247
    const { classes } = this.props;
248
    const { filterList } = this.state;
249
    if (column.filterOptions && column.filterOptions.renderValue) {
250
      console.warn('Custom renderValue not supported for textField filters');
251
    }
252
    const width = (column.filterOptions && column.filterOptions.fullWidth) === true ? 12 : 6;
7✔
253

7✔
254
    return (
255
      <Grid
7✔
256
        item
257
        key={index}
258
        xs={width}
259
        classes={{ 'grid-xs-12': classes.gridListTile, 'grid-xs-6': classes.gridListTile }}>
260
        <FormControl key={index} fullWidth>
261
          <TextField
262
            fullWidth
263
            variant={'standard'}
7✔
264
            label={column.label}
265
            value={filterList[index].toString() || ''}
266
            data-testid={'filtertextfield-' + column.name}
267
            onChange={event => this.handleTextFieldChange(event, index, column.name)}
268
          />
269
        </FormControl>
270
      </Grid>
271
    );
272
  }
273

7✔
274
  renderMultiselect(column, index, components = {}) {
275
    const CheckboxComponent = components.Checkbox || Checkbox;
4✔
276

277
    const { classes, filterData } = this.props;
278
    const { filterList } = this.state;
279
    const renderItem =
280
      column.filterOptions && column.filterOptions.renderValue ? column.filterOptions.renderValue : v => v;
281
    const width = (column.filterOptions && column.filterOptions.fullWidth) === true ? 12 : 6;
282
    return (
283
      <Grid
284
        item
285
        key={index}
286
        xs={width}
287
        classes={{ 'grid-xs-12': classes.gridListTile, 'grid-xs-6': classes.gridListTile }}>
288
        <FormControl key={index} variant={'standard'} fullWidth>
289
          <InputLabel htmlFor={column.name}>{column.label}</InputLabel>
290
          <Select
291
            multiple
292
            fullWidth
293
            value={filterList[index] || []}
294
            renderValue={selected => selected.map(renderItem).join(', ')}
295
            name={column.name}
296
            onChange={event => this.handleMultiselectChange(index, event.target.value, column.name)}
297
            input={<Input name={column.name} id={column.name} />}>
298
            {filterData[index].map((filterValue, filterIndex) => (
299
              <MenuItem value={filterValue} key={filterIndex + 1}>
300
                <CheckboxComponent
301
                  data-description="table-filter"
302
                  color="primary"
303
                  checked={filterList[index].indexOf(filterValue) >= 0}
304
                  value={filterValue != null ? filterValue.toString() : ''}
305
                  className={classes.checkboxIcon}
306
                  classes={{
307
                    root: classes.checkbox,
308
                    checked: classes.checked,
309
                  }}
310
                />
311
                <ListItemText primary={renderItem(filterValue)} />
312
              </MenuItem>
313
            ))}
314
          </Select>
315
        </FormControl>
316
      </Grid>
317
    );
318
  }
319

320
  renderCustomField(column, index) {
321
    const { classes, filterData, options } = this.props;
322
    const { filterList } = this.state;
323
    const width = (column.filterOptions && column.filterOptions.fullWidth) === true ? 12 : 6;
324
    const display =
325
      (column.filterOptions && column.filterOptions.display) ||
326
      (options.filterOptions && options.filterOptions.display);
327

328
    if (!display) {
329
      console.error('Property "display" is required when using custom filter type.');
330
      return;
331
    }
332
    if (column.filterListOptions && column.filterListOptions.renderValue) {
333
      console.warning('"renderValue" is ignored for custom filter fields');
334
    }
335

336
    return (
337
      <Grid
338
        item
339
        key={index}
340
        xs={width}
341
        classes={{ 'grid-xs-12': classes.gridListTile, 'grid-xs-6': classes.gridListTile }}>
342
        <FormControl key={index} fullWidth>
343
          {display(filterList, this.handleCustomChange, index, column, filterData)}
344
        </FormControl>
345
      </Grid>
346
    );
347
  }
348

349
  applyFilters = () => {
350
    this.state.filterList.forEach((filter, index) => {
351
      this.props.onFilterUpdate(index, filter, this.props.columns[index], 'custom');
352
    });
353

354
    this.props.handleClose(); // close filter dialog popover
355

356
    if (this.props.options.onFilterConfirm) {
357
      this.props.options.onFilterConfirm(this.state.filterList);
358
    }
359

360
    return this.state.filterList;
361
  };
362

363
  resetFilters = () => {
364
    this.setState({
365
      filterList: this.props.columns.map(() => []),
366
    });
367
    if (this.props.options.confirmFilters !== true) {
368
      this.props.onFilterReset();
369
    }
370
  };
371

372
  render() {
373
    const { classes, columns, options, customFooter, filterList, components = {} } = this.props;
374
    const textLabels = options.textLabels.filter;
375

376
    return (
377
      <div className={classes.root}>
378
        <div className={classes.header}>
379
          <div className={classes.reset}>
380
            <Typography
381
              variant="body2"
382
              className={clsx({
383
                [classes.title]: true,
384
              })}>
385
              {textLabels.title}
386
            </Typography>
387
            <Button
388
              color="primary"
389
              className={classes.resetLink}
390
              tabIndex={0}
391
              aria-label={textLabels.reset}
392
              data-testid={'filterReset-button'}
393
              onClick={this.resetFilters}>
394
              {textLabels.reset}
395
            </Button>
396
          </div>
397
          <div className={classes.filtersSelected} />
398
        </div>
399
        <Grid container direction="row" justifyContent="flex-start" alignItems="center" spacing={4}>
400
          {columns.map((column, index) => {
401
            if (column.filter) {
402
              const filterType = column.filterType || options.filterType;
403
              return filterType === 'checkbox'
404
                ? this.renderCheckbox(column, index, components)
405
                : filterType === 'multiselect'
406
                ? this.renderMultiselect(column, index, components)
407
                : filterType === 'textField'
408
                ? this.renderTextField(column, index)
409
                : filterType === 'custom'
410
                ? this.renderCustomField(column, index)
411
                : this.renderSelect(column, index);
412
            }
413
          })}
414
        </Grid>
415
        {customFooter ? customFooter(filterList, this.applyFilters) : ''}
416
      </div>
417
    );
418
  }
419
}
420

421
export default withStyles(TableFilter, defaultFilterStyles, { name: 'MUIDataTableFilter' });
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc