ameliep0032
ameliep0032

Reputation: 11

How to disable column sorting on a column where all rows have same content

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

Answers (1)

ameliep0032
ameliep0032

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

Related Questions