Additional DataTable custom sort questions

UI Components for React
Post Reply
reid
Posts: 7
Joined: 12 Jul 2014, 20:30

28 Nov 2022, 22:52

This is related to my other post, but different enough that I though I should start a new topic.

There are two things that I am trying to accomplish that I am having trouble with.

First, one of my columns represents more than one value so it can be sorted more than 2 ways. However, the order parameter of the ColumnSortParameters of the sortFunction callback only has 3 values (-1, 0, 1) for ascending, unsorted, and descending. I am able to more or less work around this limitation in the following way.

Assuming I have a functional component that is the container of my DataTable I use a custom hook that is similar to this:

Code: Select all

function useTableData(initialRows) {
    const [rows, setRows] = useState(initialRows);
    const [orders, setOrders] = useState({});
    
    // Other stuff
    
    const onColumnSort = (params) => {
        const field = params.field;
        const order = orders?.[field] || 0;
        
        const sortedRows = rows.sort((row1, row2) => {
            // getColumnValue(...) gets the appropriate value from the 
            // column based on the order
            const value1 = getColumnValue(field, row1, order);
            const value2 = getColumnValue(field, row2, order);
            
            if (a < b) { return -1; }
            if (a > b) { return 1; }
            
            return 0;
        };
        
        if (isEven(order)) { sortedRows.reverse(); }
        
        const mod = field === "columnWith2Values" ? 4 : 2;
        
        // Note this only works if I modify the object in place. If I try to
        // use the setter, I get an error 
        // that I cannot update a component while rendering a different 
        // component.
        orders[field] = (order + 1) % mod;
        
        return sortedRows;
    }
    
    return {rows, onColumnSort};
}
Then later I define my column similar to this:

Code: Select all

export default function MyComponent(props) {
    const {rows, onColumnSort} = useTableData(props.initialTableRow);
    
    // Other stuff
    return (
        <DataTable
            value={rows}
        >
            <Column
                key="columnWith2Values"
                field="columnWith2Values"
                header="Header"
                body={makeBody()}  // Creates the template for displaying the multi valued cell
                sortable
                sortFunction={onColumnSort}
            />
        </DataTable>
    );
}
The first question is, is there a better way to do this? It seems problematic that I have to modify the orders object in place and cannot use the `set` method. Searching online, the typical way to resolve the error I'm seeing is to put the mutating code in a useEffect block. However, it's not obvious how to do that here.

The second question is, how can I dynamically modify the sort icons shown in the column headers? I would like to have a different icon in the header based on the value that is being sorted upon and whether it is ascending or descending. As an alternative, I would be fine with being able to alter the header content when the column is sorted. I've tried storing the dynamic content in a state variable and changing it in the sortFunction, but I run into the same problem as before. Namely, that setting the state causes an error that I cannot update a component while rendering a different component.

Melloware
Posts: 3716
Joined: 22 Apr 2013, 15:48

02 Dec 2022, 19:04

You have a lot here but to answer some of your questions...

1. The sort icons and headers you can override with simple CSS overrides I would think to change the icons or the look and feel.

2. As for a column that represents multiple values and can be sorted multiple ways that feels tricky but your solution seems reasonable to me if you can get it working?

Also there is this old open bug not sure if it is still valid for 8.7.2: https://github.com/primefaces/primereact/issues/1980 but thought you might be interested.
PrimeFaces Developer | PrimeFaces Extensions Developer
GitHub Profile: https://github.com/melloware
PrimeFaces Elite 13.0.0 / PF Extensions 13.0.0
PrimeReact 9.6.1

reid
Posts: 7
Joined: 12 Jul 2014, 20:30

15 Dec 2022, 19:12

Thanks for the reply. I must have accidentally unchecked the box for notifying me when a reply came in, so it slipped passed me since I moved on to other parts of the project.

I've made a little bit of progress on dynamically customizing the header/icons for the columns, but I'm not quite there yet.

As before, I can use an object to store the augmented column orders (that have more than 3 values). Unfortunately, I have come to the conclusion that it is just not possible to change the state of my component within the custom sorting callback, because it triggers both the DataTable to rerender and my component to rerender, which is a react violation.

However, I noticed the onValueChange callback of the DataTable is called after the sorting is completed and does not trigger a rerender, so I can change the state of my component without triggering an error. The problem I am now facing is that the onValueChange callback seems to trigger the custom sort callback to be called twice for a single click of the header.

Using onValueChange does allow me to customize the header, but since the sort method is being called twice it is messing up the logic for what is supposed to display in the header.

To make this concrete, given the original onColumnSort function defined in my first post I have an onValueChange callback similar to the following:

Code: Select all

const onValueChange = (data: any[]) => {
    const order = orders["columnWith2Values"];
   
   const direction = isEven(order) ? "up" : "down";
   setHeaderClassName(`pi pi-arrow-${direction}`);
};
This doesn't trigger an error (and will update the header with new values), but in this case order is always odd. So the direction is always down and the icon never changes. Note, the onColumnSort is called twice, but the onValueChange is only called once.

Edit: One other thing I noticed is that simply having an onValueChange handler, even if it does nothing, causes the sortFunction to be called twice.

Any ideas on why this is happening and how to fix it?

reid
Posts: 7
Joined: 12 Jul 2014, 20:30

16 Dec 2022, 00:04

Ok, I think I've figured it out. In order to get it to work I need to sort in controlled mode. I can the set all the state I need to in the onSort method and only use the sortFunction for manipulating the table rows. This doesn't require using the onValueChange handler and so the sortFunction is only called once.

Post Reply

Return to “PrimeReact”

  • Information
  • Who is online

    Users browsing this forum: No registered users and 3 guests