I am trying to sort data in a react-table, but as new data arrives the sorts are nullified. The component containing the table gets its data from a prop. That data itself comes from redux, which is updated every 15 seconds with the latest data from the server (where it is changing frequently).
What I'd like is to sort the data, and for those sorts to stay in place as/when the data changes. What actually happens is that I sort the table by a column header, and then when the data changes the sorts are removed.
I have tried:
autoResetSortBy
to falsebut becomes this as soon as data changes:
I would like to preserve the sort filter so that as new data arrives (the same 99% of the time, but 1% of the time a column value is different or there is a new row) it is sorted by the applied sort filter.
Here's a simplified example of my code (as small as I could make it):
import * as React from 'react'
import { useTable, Column, useSortBy } from 'react-table'
import * as SharedTypes from '@shared/declarations'
interface Props {
serverData: SharedTypes.API.MarketBotSummary[]
}
export const TableTest: React.StatelessComponent<Props> = ({ serverData }: Props) => {
const [localData, setLocalData] = React.useState<SharedTypes.API.MarketBotSummary[]>(serverData)
const skipPageResetRef = React.useRef<boolean>(false)
React.useEffect(() => {
skipPageResetRef.current = true
setLocalData(serverData)
console.log('data changed', serverData)
}, [serverData])
React.useEffect(() => {
// After the table has updated, always remove the flag
skipPageResetRef.current = false
})
const Table = ({ columns, data }: { columns: Column<SharedTypes.API.MarketBotSummary>[], data: SharedTypes.API.MarketBotSummary[]}) => {
// Use the state and functions returned from useTable to build your UI
const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
prepareRow,
// @ts-ignore
state: { sortBy }
} = useTable(
{
columns,
data,
// @ts-ignore
// autoResetSortBy: false,
// autoResetFilters: false,
autoResetPage: !skipPageResetRef.current,
autoResetExpanded: !skipPageResetRef.current,
autoResetGroupBy: !skipPageResetRef.current,
autoResetSelectedRows: !skipPageResetRef.current,
autoResetSortBy: !skipPageResetRef.current,
autoResetFilters: !skipPageResetRef.current,
autoResetRowState: !skipPageResetRef.current,
},
useSortBy
)
// Render the UI for your table
return (
<>
<table {...getTableProps()} className="ui celled very compact structured table">
<thead>
{
// @ts-ignore
headerGroups.map(headerGroup => (
<tr {...headerGroup.getHeaderGroupProps()}>
{
// @ts-ignore
headerGroup.headers.map(column => (
<th {
// @ts-ignore
...column.getHeaderProps(column.getSortByToggleProps())
}>
{column.render('Header')}
<span>
{
// @ts-ignore
column.isSorted ? column.isSortedDesc ? ' 🔽' : ' 🔼' : ''
}
</span>
</th>
))}
</tr>
))}
</thead>
<tbody {...getTableBodyProps()}>
{
// @ts-ignore
rows.map((row) => {
prepareRow(row)
return (
<tr {...row.getRowProps()}>
{row.cells.map(
// @ts-ignore
cell => <td {...cell.getCellProps({ className: cell.column?.className?.(cell.value, row.original) })}>
{cell.render('Cell')}
</td>
)}
</tr>
)
})}
</tbody>
</table>
<pre>
<code>
{JSON.stringify(
{
sortBy,
},
null,
2
)}
</code>
</pre>
</>
)
}
const columns = React.useMemo(
() => [
{
Header: 'Data',
columns: [
{
Header: 'Quote',
accessor: 'quote',
},
{
Header: 'Symbol',
accessor: 'symbol',
},
{
Header: 'Mode',
accessor: 'status',
},
{
Header: 'direction',
accessor: 'tradeDirection',
},
],
},
],
[]
)
const data = React.useMemo(
() => localData,
[localData]
)
return (
<Table columns={columns} data={data} />
)
}
export default TableTest
Don't declare React components inside other React components. Every time a React functional component rerenders it recreates everything declared in its function body, while retaining references to memoised variables coming from various hooks useState
, useCallback
, useMemo
e.t.c.
When you declare a component inside another component's body, you are getting a new component on every render, which will not retain any internal state it had from the previous render. Hence your UI selections are being removed.
Declare Table
in a separate file and import it into TableTest
. Alternatively, just move everything inside Table
into the body of TableTest
.
I would also get rid of both your useMemo
hooks since they are not achieving anything. In the first case, if you have a static array/object just declare it outside the scope of the component, while in the second case, localData
is already effectively memoised by the state hook.