How can I remove column filters from a table in Antdesign on programmatical removal of columns?

325 Views Asked by At

In a ReactJS application (Typescript), using Antdesign 4.12.2, I am building a table that shows a list of record data over a number of columns. The columns shown can be modified by adding or removing them through a popover with a tree containing the various options.Columns may have associated filters.

It all works well, until you activate a filter and then remove the column. The column is then removed, but the filter is not deactivated, which means that the number of records shown does not go back to the number from before the filter was activated. When re-adding the column, the filter previously activated is still active.

How do I make sure that when a column is removed, any active filters are disabled?

Below is my code.

Note that as per this suggestion I've tried changing the filteredValue of a column to null, but does not appear to have any effect in the useEffect. Interestingly, using the 'clear' button, that I've added for debugging, does seem to work to a degree. It appears to remove all filters and shows all records after that. This only works when the column is visible, however. It does nothing after the column has been removed. Also, if you remove the column and re-add it after this, the filter will appear to work again. But if you re-added it after leaving the filters deselected, it will come back activated with the original filter selected. It's a hot mess!

Edit: I've updated the code below to also track and update filteredInfo, as per this article.

const isPresent = <T,>(t: T | undefined | null): t is T => t != null;

interface User {
    username: string,
    status: string
}

const ReportItemsTable: React.FC = () => {
    const allColumnOptions = [
        {
            key: "username", title: "Username"
        },
        {
            key: "status", title: "Status"
        }
    ];

    const items: User[] = [
        {
            status: "one",
            username: "Piet"
        },
        {
            status: "two",
            username: "Saar"
        },
        {
            status: "one",
            username: "Jan"
        },
        {
            status: "two",
            username: "Klaas"
        },
        {
            status: "one",
            username: "Maartje"
        },
        {
            status: "three",
            username: "Sjeng"
        },
        {
            status: "one",
            username: "Elke"
        },
        {
            status: "two",
            username: "Twan"
        },
        {
            status: "one",
            username: "An"
        },
        {
            status: "three",
            username: "Nel"
        },
    ];

    const [filteredInfo, setFilteredInfo] = useState<Record<string, FilterValue | null>>({});


    const possibleColumns: ColumnType<User>[] = [
        {
            title: "Username",
            key: "username",
            dataIndex: "username",
            sorter: (a: User, b: User) => a.username.localeCompare(b.username),
            render: (userName: string) => (<>{userName}</>)
        },
        {
            title: "Status",
            key: "status",
            dataIndex: "status",
            filteredValue: filteredInfo && filteredInfo["status"],
            filters: [
                {
                    text: "One",
                    value: "one"
                },
                {
                    text: "Two",
                    value: "two"
                },
                {
                    text: "Three",
                    value: "three"
                },
            ],
            onFilter: (value: ColumnFilterValue, user: User) => user.status === value,
            sorter: (a: User, b: User) => a.status.localeCompare(b.status),
            render: (status: string) => (<>{status}</>)
        }
    ];

    // Note that selectedColumns is controlled by checkedKeys. selectedColumns should not be modified directly.
    const [checkedKeys, setCheckedKeys] = useState<Key[] | { checked: Key[]; halfChecked: Key[]; }>([]);
    const [selectedColumns, setSelectedColumns] = useState<ColumnType<User>[]>(possibleColumns);

    const getTreeData = (allOptions: DataNode[]): DataNode[] => {
        return [{
            key: "masterKey",
            title: "(de)select all",
            children: [...allOptions]
        }];
    };

    // The useEffect below was originally only in charge of checking 
    // whether the checkedKeys had changed and accordingly adding or 
    // removing columns from the table. It was expanded to set the 
    // filteredValue to null if the column was unchecked. 
    useEffect(() => {
        setFilteredInfo(filteredInfo => {
            const newFilteredInfo = {...filteredInfo};
            const visibleKeys = Array.isArray(checkedKeys) ? checkedKeys : checkedKeys.checked;
            const invisibleKeys = possibleColumns.map(column => column.key).filter(isPresent).filter(columnKey => !visibleKeys.includes(columnKey));
            invisibleKeys.forEach(key => delete newFilteredInfo[key]);
            return newFilteredInfo;
        });

        if (Array.isArray(checkedKeys)) {
            setSelectedColumns(possibleColumns
                .map(column => {
                    if (!isPresent(column.key)) return column;
                    if (!checkedKeys.includes(column.key)) {
                        return {
                            ...column,
                            filteredValue: null
                        }
                    }
                    return column;
                })
                .filter(column => checkedKeys
                    .map(checkedKey => checkedKey.toString())
                    .includes(column.key?.toString() ?? "")));
        } else {
            setSelectedColumns(possibleColumns
                .map(column => {
                    if (!isPresent(column.key)) return column;
                    if (!checkedKeys.checked.includes(column.key)) {
                        return {
                            ...column,
                            filteredValue: null
                        }
                    }
                    return column;
                })
                .filter(column => checkedKeys.checked
                    .map(checkedKey => checkedKey.toString())
                    .includes(column.key?.toString() ?? "")));
        }
    }, [checkedKeys]);

    const filterTree =
        <Tree
            key={"columnfilter"}
            checkable
            defaultExpandedKeys={["masterKey"]}
            checkedKeys={checkedKeys}
            selectable={false}
            onCheck={checkedKeys => {
                setCheckedKeys(checkedKeys);
            }}
            treeData={getTreeData(allColumnOptions)}
        />
    ;

    const columnFilterButton =
        <Popover
            placement="bottom"
            title={"Select columns"}
            content={filterTree}
            trigger="click">
            <Tooltip title={"Select columns"}>
                <Button icon={<ViewWeekOutlinedIcon sx={{ fontSize: 17, marginTop: "2.5px" }}></ViewWeekOutlinedIcon>}>
                </Button>
            </Tooltip>
        </Popover>
    ;

    return (
        <>
            <Row style={{paddingTop: "10px", paddingBottom: "10px"}}>
                <Col span={24} style={{ textAlign: "left" }}>
                    <Space>
                        <Button.Group>
                            {columnFilterButton}
                        </Button.Group>
                    </Space>
                </Col>
            </Row>
            <Table
                onChange={(pagination, filters) => setFilteredInfo(filters)}
                columns={selectedColumns}
                dataSource={items}
                size="middle"
            />
            <button onClick={() => {
                setFilteredInfo({});

                setSelectedColumns(selectedColumns.map(column => {
                    return {
                        ...column,
                        filteredValue: null
                    };
                }));
            }
            }>clear</button>
        </>
    );
};

export default ReportItemsTable;
0

There are 0 best solutions below