New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
expose currentDataSource of Table, update when dataSource/filters/sorters are changed by application #24022
Comments
Is "inactive" because nobody interested in it or nobody read it at all? |
the event named "change" can get { currentDataSource }. |
onChange fires only when USER changed something, but when app sets/updates data to table this event is NOT fired. To have currentDS always I proposed this addition. Especially, because it is really simple to implement in v4+: one line of code, just after creating DS need to add effect, that fires event with the data. |
I'd like to second this. Without support of ref on the table either, it seems there is no clean way to track the current values displayed. For example, if a user were to select rows, then apply a programmatic filter, there seems to be no good way to know which of the checked boxes are not filtered out. |
Any update for this feature request? |
try destructuring the dataSource data. it works in my case |
How does this allow you to keep up with new dataSource after applying a programmatic filter? |
Would also love this feature. Currently having to use a janky workaround with |
Having the same problem. There should be an easy possibility to access the filtered data. |
+1 |
+1 We are waiting for this development to export, also... This development will allow column filters to be sorted based on current data. It works for many other things. Edit: This development should also cover default filters. Access with ref may be the most accurate way. |
I almost missed the @nickkrein comment, using the summary={(currentData) => {
onSortOrFilter(currentData)
return null // or whatever you want
}} Thank you @nickkrein 👍 |
This only works for the current page. |
Oh, that's right. I don't have pagination in my use case but thanks you for pointing that 🙏 |
What about if i want to fetch data from api if user choose an item from filter list ?? i have a problem with that and i think the problem is |
Having the same problem. |
I kind of have the same problem too. Why not expose a |
Hi why is it taking this long to fix, this should be very simple .. apparently I need on change to be triggered when filters and sorting is done programmatically or when table data is changed.. |
I think I have the same problem (or at least a similar problem with the same solution): I have a table with Data on which I apply front-end filters (no pagination). The data repeatedly gets updated when extra calls are being done to the backend, but this does not trigger the onChange. Because of this, the counters for my filtered data and total data don't match, unless I manually apply these filters outside of the table. It would be a lot easier if there was a certain fix for this, now I'm just working in circles to update the table/filters when data is updated. |
What I did was extract the currentDataSource to first define
|
@zxlvera This doesn't solve the problem. Because onChange event with default filter is not triggered. (Example: I cannot export a default filtered data to an excel file.) |
Come on! |
Any updates on this one? |
Any updates? |
In this file https://github.com/ant-design/ant-design/blob/master/components/table/Table.tsx Just adding
would solve the issue. I am new to open-source contributions, it would be great if someone could help me add this feature or add it by themselves. |
I'm using
The issue with this trick is that it only shows the current page data |
this worked for me ! thx |
So, has any problem to prevent solving this issue? |
It's very frustrating that this issue still hasn't been resolved years later. As a result, exporting table data is hacky, at best, and impossible, at worse. |
+1 |
2 similar comments
+1 |
+1 |
We found a solution for this. It is very much a hack, but appears to work. You need a column where you don't need a filter interface. For example, we have an "Action" column with buttons in it, so we put the hidden filter there. There is a full working example here https://codesandbox.io/s/customized-filter-panel-antd-5-9-4-forked-6tfqtw?file=/demo.tsx:5542-5597 import React, { useEffect, useRef, useState } from 'react'
import {Table} from 'antd'
const TableWithRestoredState = () => {
// used to track if the Table has loaded and trigger a re-render
const [loaded, setLoaded] = useState(false)
// used to store a ref to the callback from antd
const triggerDataRef = React.useRef<() => void | void>(null)
// count the renders so we only do this once
const renderTimesRef = React.useRef<number>(0)
useEffect(() => {
if (renderTimesRef.current === 1 && triggerDataRef.current) {
triggerDataRef.current()
}
renderTimesRef.current += 1
}, [loaded])
const columns = [
{
title: "My Column",
filterDropdown: ({ setSelectedKeys, confirm }) => {
if (!triggerDataRef.current) {
triggerDataRef.current = () => {
setSelectedKeys(["fake key"])
confirm()
};
setLoaded(true)
}
return <div></div>
},
onFilter: (value, record) => {
return true; // needed so no rows are actually filtered by our fake filter
},
filterIcon: () => {
return <div></div>
}
}
]
// filtered data will store a local copy of the sorted or filtered data from the antd Table
const [filteredData, setFilteredData] = useState([])
const handleChange = (pagination, filters, sorter, extra) => {
if (extra.action === "filter" || extra.action === "sort") {
setFilteredData(extra.currentDataSource)
}
}
return <Table columns={columns} dataSource={data} onChange={handleChange} />
} |
None of the above workarounds were working 100% for me (extra column shown + change not triggered on load when I have GET parameters filtering the table or change not triggered when clearing a filter). Here is a fully working approach:
With TypeScript, this makes it a drop-in replacement for How to use it: <TableWithFilteredRecordsCallback
dataSource={your_data_as_usual}
rowKey={…} // rowKey is mandatory for this to work.
columns={…} // your columns must provide key or dataIndex.
onDataFiltered={(currentRecords) => {
// Do what you need in here!
}}
// other usual Table props.
/> And now the modified Table itself (sorry, a bit long, could not find how to make it collapsible): // TableWithFilteredRecordsCallback.tsx
import { Table, TablePaginationConfig, TableProps } from "antd";
import { ColumnType } from "antd/es/table/interface";
import React, { useCallback, useEffect, useMemo, useRef } from "react";
import type { FilterValue, SorterResult, TableCurrentDataSource } from "antd/lib/table/interface";
function getColumnKey<RecordType extends object>(
column: ColumnType<RecordType>,
columnIndex: number
) {
let columnKey = column.key ?? ("dataIndex" in column ? column.dataIndex : undefined);
if (typeof columnKey === "string") {
return columnKey;
}
if (columnKey) {
throw Error(`number or string[] dataIndex is not supported.`);
}
throw Error(
`Column with index ${columnIndex} did not have \`key\` defined, required by \`TableWithFilteredRecordsCallback\`.`
);
}
export default function TableWithFilteredRecordsCallback<RecordType extends object>({
dataSource,
columns,
rowKey,
onChange,
onDataFiltered,
...otherProps
}: TableProps<RecordType> & {
rowKey: TableProps<RecordType>["rowKey"]; // Makes rowKey mandatory.
onDataFiltered: (currentRecords: RecordType[]) => void;
filterTriggerDebounceMs?: number;
}) {
const filteredCellsRef = useRef<{
[columnKey: string]: { [recordKey: React.Key]: boolean };
}>({});
const filterValuesRef = useRef<{ [columnKey: string]: FilterValue | null }>({});
const getRecordKey = useCallback(
(record: RecordType) => {
// Due to bad typing detection.
if (rowKey === undefined) {
return undefined;
}
if (typeof rowKey === "string") {
// @ts-ignore
return record[rowKey];
}
return rowKey(record);
},
[rowKey]
);
const triggerFilterCallback = useCallback(() => {
const columnsFilteredCells = Object.values(filteredCellsRef.current);
onDataFiltered(
(dataSource ?? []).filter((row) => {
const recordKey = getRecordKey(row);
return columnsFilteredCells.every((records) => records[recordKey] ?? true);
})
);
}, [onDataFiltered, dataSource, getRecordKey]);
useEffect(() => {
triggerFilterCallback();
}, [triggerFilterCallback]);
const interceptedColumns = useMemo(() => {
if (columns === undefined) {
return undefined;
}
return columns.map((column, index) => {
const columnKey = getColumnKey(column, index);
const originalOnFilter = column.onFilter;
if (originalOnFilter === undefined) {
return column;
}
if (!(columnKey in filteredCellsRef.current)) {
filteredCellsRef.current[columnKey] = {};
}
const columnFilteredCells = filteredCellsRef.current[columnKey];
return {
...column,
onFilter: (value: string | number | boolean, record: RecordType) => {
const isFiltered = originalOnFilter(value, record);
columnFilteredCells[getRecordKey(record)] = isFiltered;
return isFiltered;
},
};
});
}, [columns, getRecordKey]);
const modifiedOnChange = useCallback(
(
pagination: TablePaginationConfig,
filters: Record<string, FilterValue | null>,
sorter: SorterResult<RecordType> | SorterResult<RecordType>[],
extra: TableCurrentDataSource<RecordType>
) => {
if (onChange !== undefined) {
onChange(pagination, filters, sorter, extra);
}
let filtersHaveChanged: boolean = false;
for (const [columnKey, filterValue] of Object.entries(filters)) {
if (filterValuesRef.current[columnKey] === filterValue) {
continue;
}
filterValuesRef.current[columnKey] = filterValue;
filtersHaveChanged = true;
if (filterValue === null) {
const columnFilteredCells = filteredCellsRef.current[columnKey];
// This filter is cleared. Since clearing a filter (or unchecking all selected items)
// does not call onFilter, this is our only way to detect that we have to clear this filter.
// We therefore simulate a call to onFilter with no value selected in that filter.
for (const recordKey of Object.keys(columnFilteredCells)) {
columnFilteredCells[recordKey] = true;
}
}
}
if (filtersHaveChanged) {
triggerFilterCallback();
}
},
[triggerFilterCallback, onChange]
);
return (
<Table<RecordType>
dataSource={dataSource}
columns={interceptedColumns}
onChange={modifiedOnChange}
{...otherProps}
/>
);
} |
What problem does this feature solve?
It allows to perform operations on filtered&sorted data, e.g. export data to file, calculate number of items in table, etc.
The problem is that
currentDataSource
that I receive viaonChange
is not updated when dataSource/filters/sorters are changed by application.What does the proposed API look like?
Event prop
onCurrentDataSourceChanged
, which should be fired after each calculation ofcurrentDataSource
, on any related change and initial.If it is possible, please add it to both 3.* and 4.* versions of Ant Design.
The text was updated successfully, but these errors were encountered: