Search code examples
reactjsreact-tablecolumnsorting

React-table: How to sort table data based on the changed cell value (override the default sorting with cell props.value)?


Background

I have a table built using react-table with useSortBy hook. Everything works just fine until I change what Cell returns from columns. The thing is, for certain columns I have to modify how data is displayed on the table (without altering the data itself). For example, I have a column on the table that doesnt just display the raw data, it has to combine other values to that data, then display the result. Take a look at the sample code below.

Sample Code

import { useContext, useMemo } from "react";
import "../assets/css/result.css";
import { useTable, useSortBy } from "react-table";
import globalContext from "../globalContext/globalData";

const Result = () => {
    const { searchResults } = useContext(globalContext);

    const columns = useMemo(() => headers, []); // headers defined below

    const data = useMemo(() => searchResults, [searchResults]);

    return (
        <section id="result">
            <div className="title">
                <p>Here is my fancy table</p>
            </div>
            <div className="table">
                <Table columns={columns} data={data} />
            </div>
        </section>
    );
};

export default Result;

function Table({ columns, data }) {
    const { getTableProps, getTableBodyProps, headerGroups, rows, prepareRow } = useTable(
        {
            columns,
            data,
        },
        useSortBy
    );

    return (
        <table {...getTableProps()}>
            <thead>
                {headerGroups.map(headerGroup => (
                    <tr {...headerGroup.getHeaderGroupProps()}>
                        {headerGroup.headers.map(column => (
                            // Add the sorting props to control sorting. For this example
                            // we can add them into the header props
                            <th {...column.getHeaderProps(column.getSortByToggleProps())}>
                                {column.render("Header")}
                                {/* Add a sort direction indicator */}
                                <span>{column.isSorted ? (column.isSortedDesc ? "⇩" : "⇧") : ""}</span>
                            </th>
                        ))}
                    </tr>
                ))}
            </thead>
            <tbody {...getTableBodyProps()}>
                {rows.map((row, i) => {
                    prepareRow(row);
                    return (
                        <tr {...row.getRowProps()}>
                            {row.cells.map(cell => {
                                return (
                                    <td {...cell.getCellProps({ className: cell.column.className })}>
                                        {cell.render("Cell")}
                                    </td>
                                );
                            })}
                        </tr>
                    );
                })}
            </tbody>
        </table>
    );
}

const headers = [
    {
        Header: "Normal Column",
        accessor: "normal_column",
        // Cell: props => props.value, // default behaviour
    },
    {
        Header: "Order count",
        accessor: "order_count",
        Cell: props => `${Number(props.value).toLocaleString("en")} 個`, // 5510 => 5,510 個
        // here sorting works fine as the cell display value hasnt changed much
    },
    {
        Header: "Other Column",
        accessor: "column_name",
        // BUG: sorting does not work properly as it still uses props.value
        Cell: props => {
            // Here I have to display the value in a different way, combining somethings from the original row data
            // displays as expected, but the sorting is still done based on the default props.value under the hood
            const { var1, var2, var3 } = props.row.original;
            return `${var1}-${var2}-${var3}`; // this should be what sorting is based on, not props.value
        },
    },
];

Problem

Let's say I change a cell display value from the original 5510 to 5,510 個 (format the number with commas as thousands separators and put space+個 at the end). Sorting works fine with it as sorting results of 5,510 個 and 5510 values do not differ so much.
Here is when it gets tricky: I need a new column that combines two or three row values and outputs a certain display value as shown inside the headers object of the Sample Code. Now the sorting does not work as expected (based on the return value of the Cell).

What I tried

I tried overriding the props.value from Cell function inside my headers object. But it is immutable. So this did not work. How should I go about figuring this out? What am I missing?


Solution

  • What about instead of trying to set the displayed value inside of your Cell function you do it before passing the props to your Table component, this is the only way I can think of without the changing the original sorting function from the react table hook since it's looking to sort directly with the props.value you provide at the beginning.

    Maybe something like this:

    
    const formatMyData = (searchResults)=>{
     return searchResults.map((result)=>({...result, myFormattedColumn: `${result.data1}-${result.data2}-${result.data3}`}))
    
    }
    
    const data = useMemo(() => searchResults, [formatMyData(searchResults)]);