Reputation: 11
I'm working on a React app which uses classes and it also uses react-table v6.8.6 to render some data on the page. I want to be able to custom the sort functionality available on column headers to suit my UX requirements, but I have no idea how to get started on it!
Here's an example github repo where I've build a simplified version of my situation, and with fake data, which helps visualise the issue if you want to run it locally.
From the example repo, this is the package.json:
{
"name": "react-table-v6",
"version": "0.1.0",
"private": true,
"dependencies": {
"autoprefixer": "^10.4.19",
"moment": "^2.29.4",
"postcss": "^8.4.38",
"react": "^16.14.0",
"react-dom": "^16.14.0",
"react-scripts": "^5.0.1",
"react-table": "^6.8.6",
"tailwindcss": "^3.4.3"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}
This is the content of my App in index.js file where my table is:
import React from "react";
import { render } from "react-dom";
import {
makeData,
Tips,
datetimeTinyFormatterUTC,
sortDatetimes,
} from "./Utils";
import './index.css';
import ReactTable from "react-table";
import "react-table/react-table.css";
class App extends React.Component {
constructor() {
super();
this.state = {
data: makeData(),
};
}
render() {
const { data } = this.state;
return (
<div>
<ReactTable
data={data}
columns={[
{
Header: "Date",
id: "arrivalDatetime",
accessor: (item) => (
datetimeTinyFormatterUTC(item.arrivalDatetime)
),
minWidth: 100,
maxWidth: 200,
Cell: props => <div>{props.value}</div>,
sortMethod: (a, b) => sortDatetimes(a, b)
},
{
Header: "Destination",
id: "destination",
//here I might get the origin and destination strings as 'UNKNOWN', which would make all strings the same in all rows
accessor: flight => flight.origin + ' > ' + flight.destination,
Cell: props => <div>{props.value}</div>,
minWidth: 100,
maxWidth: 200,
},
{
id: "savings",
Header: "Savings",
accessor: trip => {
const saved = trip.test.reduce((total, b) => {
if(b.succeeded && b.saved > 0) {total += b.saved}
return total;
}, 0);
return saved;
},
minWidth: 100,
maxWidth: 200,
Cell: props => <div>{props.value} dollars</div>
},
{
Header: "success",
id: "success",
accessor: item => {
const succeeded = item.succeededCount;
const total = item.overall;
return { succeeded, total };
},
minWidth: 100,
maxWidth: 200,
Cell: item => <div>{item.value.succeeded + ' / ' + item.value.total}</div>,
sortMethod: (a, b) => a.succeeded > b.succeeded ? 1 : -1
},
{
id: "projected-savings",
Header: "Max Savings",
accessor: () => {
// here I have more logic here which might lead me to get a 0 in all rows
const saved = 0;
return saved;
},
minWidth: 100,
maxWidth: 200,
Cell: props => <div>{props.value} dollars</div>
},
]}
defaultSorted={[
{
id: 'arrivalDatetime',
desc: false
}
]}
sortable={true}
defaultPageSize={10}
className="-striped -highlight"
/>
<br />
<Tips />
</div>
);
}
}
render(<App />, document.getElementById("root"));
This is the utils.js file with utils functions that help create and format the data:
import React from "react";
import moment from "moment";
import "./index.css";
const range = (len) => {
const arr = [];
for (let i = 0; i < len; i++) {
arr.push(i);
}
return arr;
};
const getRamdomDateInBetween = (start, end) => {
start = Date.parse(start);
end = Date.parse(end);
return new Date(Math.floor(Math.random() * (end - start + 1) + start));
};
const newRow = () => {
return {
arrivalDatetime: getRamdomDateInBetween("2023-01-01", "2023-02-28"),
test: [{
succeeded: true,
saved: Math.floor(Math.random() * 1500),
}, {
succeeded: true,
saved: Math.floor(Math.random() * 100),
}, {
succeeded: true,
saved: Math.floor(Math.random() * 300),
}, {
succeeded: false,
saved: Math.floor(Math.random() * 1000),
}
],
origin: "XXX",
destination: "YYY",
durationInMinutes: Math.floor(Math.random() * 100),
id: "822e7a0f-10f7-4de1-bdc4",
savings: 1798,
succeededCount: Math.floor(Math.random() * 4),
overall: 4,
};
};
export function makeData(len = 12) {
return range(len).map((d) => {
return {
...newRow(),
};
});
}
export const datetimeTinyFormatterUTC = (dateTimeUnix) => {
return moment.utc(dateTimeUnix).format("MMM DD, HH:mm");
};
export const sortDatetimes = (a, b) => {
if (moment(a, "MMM DD, hh:mm a").isBefore(moment(b, "MMM DD, hh:mm a"))) {
return 1;
}
return -1;
};
export const Tips = () => (
<div style={{ textAlign: "center" }}>
<em>Tip: Hold shift when sorting to multi-sort!</em>
</div>
);
The index.css file has nothing relevant in it yet.
I have a table with a few columns: Date, Destination, Savings, Success and Max-savings, and in each column I am either processing the data to display it in a certain format or I am processing 2 fields in my object to get some new values/combine values and I do a custom display of the result.
Here's a screenshot of the table: react-table-v6-example
When I get a whole column with the same data in all rows, like the same number in all rows (see Max-savings column) or the same string (see Destination column), clicking the header just flips the original data's order back and forth,, which seems a bit pointless and my UX team would prefer if the user was not able to sort on that column in those situations. I have no idea how I could enable clicking on header/sorting on such a condition.
Do I need a sortMethod and what would it look like to achieve this? Or how would I maybe remove the click functionality on the header of a specific column? Or any other approach that I can't think about?
Note, if you go to my github repo, I've copied in the readme all the react-table v6 information across from the tanstack v6 repo so you can check it in either location.
Upvotes: 0
Views: 53
Reputation: 11
After playing around with it for a while, I've actually managed to get something working that allows me to do that.
I'm adding the sortable prop on the relevant column and make it conditional on a boolean that I get by checking the data array. It means I'm adding the following at the beginning of the render:
const { data } = this.state;
const noOrigin = data.every((item)=> item.origin === "XXX" )
const noSavings = data.every((item)=> item.savings === data[0].savings)
and in a column definition on the sortable field:
{
Header: "Destination",
id: "destination",
//here I might get the origin and destination strings as 'UNKNOWN', which would make all strings the same in all rows
accessor: flight => flight.origin + ' > ' + flight.destination,
Cell: props => <div>{props.value}</div>,
minWidth: 100,
maxWidth: 200,
sortable: !!noOrigin ? false : true
},
I don't have much data to go through, I don't think it'll ever be over a hundred items in my array so that's probable not too bad. But if anyone has a better suggestion, I'd be keen to see it :)
Upvotes: 0