Search code examples
javascriptreactjstypescriptantd

How to remove filter options from an Antd table when a filter is selected?


I'm trying to remove filter options from a table once a filter has been selected. For example, if you filter for X then filter options that don't exist on the current dataset shouldn't be available to be selected from since they won't show any rows.

minimum reproducible example:

import * as React from 'react';
import { Row, Col, Table, TableProps } from 'antd';
import "antd/dist/antd.css";
import { ColumnsType } from 'antd/lib/table';
import { ColumnFilterItem } from 'antd/lib/table/interface';

interface DataType {
  key: React.Key;
  name: string;
  age: number;
  address: string;
}

const dataSource = [
  {
    key: '1',
    name: 'Mike',
    age: 32,
    address: '10 Downing Street',
  },
  {
    key: '2',
    name: 'John',
    age: 42,
    address: '10 Downing Street',
  },
];

const App: React.FC = () => {
  const [data, setData] = React.useState<DataType[]>();

  const columns: ColumnsType<DataType> = [
    {
      key: 'name',
      title: 'Name',
      dataIndex: 'name',
      filters: dataSource.map(ds => { return {text: ds.name, value: ds.name} as ColumnFilterItem}),
      onFilter: (value, record) => record.name.indexOf(value as string) === 0,
    },
    {
      title: 'Age',
      dataIndex: 'age',
      key: 'age',
      filters: dataSource.map(ds =>{ return {text: ds.age, value: ds.age} as ColumnFilterItem}),
      onFilter: (value, record) => record.age === value
    },
    {
      title: 'Address',
      dataIndex: 'address',
      key: 'address',
    },
  ];


  const onChange: TableProps<DataType>['onChange'] = (pagination, filters, sorter, extra) => {
    // Resets the data to hopefully rebuild table with updated column filters.
    // I don't think it works that way though.
    setData(extra.currentDataSource);
    console.log('Current Data:',extra.currentDataSource);
    console.log('params', pagination, filters, sorter, extra);
  };

  React.useEffect(() => {
    setData(dataSource);
  }, [data, setData]);

  return (
    <div style={{margin: '1rem', padding: '1rem'}}>
      <Row>
        <Col>
          <Table dataSource={data} columns={columns} onChange={onChange} />
        </Col>
      </Row>
    </div>
  );
}

export default App;

From example above:

enter image description here

enter image description here

I want the highlighted option to be removed from the filter options because the filtered table doesn't have any rows with "42" in them now, just not sure how you go about doing that correctly. I thought you might be able to update the table source so the filter functions populating the options won't show them, but that doesn't seem to trigger a full re-render. What would be a better way to accomplish this?

-- edit --

To be clear, I'm trying to look at what is in the current dataset in the table and remove filters that don't apply to the data. Using the example images above, if none of the records have 42 as an option, then that filter should be removed and only 32 is an available option. The same thing should apply to all filters too, not just a single column.


Solution

  • Ok, so the solution is a bit more complex than my previous attempt. First, you want to filter your data to only the data that has been filtered. Then, using that filtered data, you rebuild your filters for each column using the filtered data. You still want to use a columns state. Same demo link, but with revised code. Hope this answers your issue. Apologies for misreading your problem earlier.

    const onChange = (pagination, filters, sorter, extra) => {
      setColumns(() => {
        // 1. filter data
        let dataCopy = [...data];
    
        for (const [k, v] of Object.entries(filters)) {
          if (v) {
            // get onFilter callback of that column
            const filterCb = COLUMNS.find((e) => e.dataIndex === k).onFilter;
            dataCopy = dataCopy.filter((e) => filterCb(v, e));
          }
        }
    
        // 2. rebuild columns using only the filtered data
        const columns = COLUMNS.map((e) => ({ ...e }));
    
        for (const colConfig of columns) {
          if (colConfig.filters) {
            const colFilters = [...colConfig.filters];
            const newColFilters = [];
    
            // iterate over filtered data to retain only values that
            // match the onFilter condition of the current colConfig
            for (const record of dataCopy) {
              const filterRecord = colFilters.find((e) =>
                colConfig.onFilter(e.value, record)
              );
              // add this filterRecord to newColFilter if not exists already
              if (
                filterRecord &&
                newColFilters.findIndex((e) => e.value === filterRecord.value) ===
                  -1
              ) {
                newColFilters.push(filterRecord);
              }
            }
            colConfig.filters = newColFilters;
          }
        }
    
        // return modified columns
        return columns;
      });
    };
    

    DEMO