Search code examples
reactjsreduxreact-hooksreact-table

react-table rows sort filter resetting on data change


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:

Sorting works: enter image description here

but becomes this as soon as data changes: enter image description here

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

Solution

  • 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.