Search code examples
reactjstypescriptsortingrenderingmui-datatable

How can I sort using MUI Datatable using value, instead of what's in my custom body render?


Before implementing the custom filter options, the temperature (of type Number) was displayed as a string with the "° C" appended to it, but still held the value, as it was able to be sorted accordingly (ascending and descending). After implementing the custom filter options, I am no longer able to filter based on the value but it filters based on the string that is returned from customBodyRender. For example, if I specify a minimum value as 3, values like 20 and 10 will not be returned, as it seems like it's now sorting alphanumerically. When I remove the customBodyRender, it works perfectly.

How can I get the MUI Data table to render the way I'd like it to, while preserving the sorting of the actual value itself? I pasted the code below. Being that I'm new to react, I will continue to do more research and update the question over time.

const columns = [
    {name: "Temperature", options:{
      filter: true,
      customBodyRender: (value) => {
        return (value + "° C")
      },
      filterType: 'custom',
      filterList: [],
      customFilterListOptions: {
        render: value => {
          if (value[0] && value[1]) {
            return `Min Temp: ${value[0]}, Max Temp: ${value[1]}`;
          } else if (value[0]) {
            return `Min Temp: ${value[0]}`;
          } else if (value[1]) {
            return `Max Temp: ${value[1]}`;
          }
          return false;
        },
        update: (filterList, filterPos, index) => {
          console.log('customFilterListOnDelete: ', filterList, filterPos, index);

          if (filterPos === 0) {
            filterList[index].splice(filterPos, 1, '');
          } else if (filterPos === 1) {
            filterList[index].splice(filterPos, 1);
          } else if (filterPos === -1) {
            filterList[index] = [];
          }

          return filterList;
        },
      },
      filterOptions: {
        names: [],
        logic(value, filters) {
          if (filters[0] && filters[1]) {
            return (value < filters[0]) || value > filters[1];
          } else if (filters[0]) {
            return value < filters[0];
          } else if (filters[1]) {
            return value > filters[1];
          }
          return false;
        },
        display: (filterList, onChange, index, column) => (
          <div>
            <FormLabel>Temperature</FormLabel>
            <FormGroup row>
              <TextField
                label="min"
                value={filterList[index][0] || ''}
                onChange={event => {
                  filterList[index][0] = event.target.value;
                  onChange(filterList[index], index, column);
                }}
                style={{ width: '45%', marginRight: '5%' }}
              />
              <TextField
                label="max"
                value={filterList[index][1] || ''}
                onChange={event => {
                  filterList[index][1] = event.target.value;
                  onChange(filterList[index], index, column);
                }}
                style={{ width: '45%' }}
              />
            </FormGroup>
          </div>
        ),
      },
    }}
]

Solution

  • I found two little problems in your code.

    First one: The render function in customFilterListOptions should return a string or string[] (see documentation). Second one: In the logic function of filterOptions it was necessary to convert values and filters to numbers before comparing them.

    If you have any questions, let me know.

    This shall work now:

    import React from "react";
    import ReactDOM from "react-dom";
    import MUIDataTable from "mui-datatables";
    import { TextField, FormLabel, FormGroup } from "@material-ui/core";
    import { toNumber } from "lodash";
    
    import "./styles.css";
    
    const TEMPERATURE_PREFIX = "° C";
    
    function App() {
      const columns = [
        {
          name: "city",
          label: "City",
          options: {
            filter: true,
            sort: false
          }
        },
        {
          name: "temp",
          label: "Temperature",
          options: {
            sort: true,
            customBodyRender: value => {
              return value + TEMPERATURE_PREFIX;
            },
            filter: true,
            filterType: "custom",
            filterList: [],
            customFilterListOptions: {
              render: value => {
                if (value[0] && value[1]) {
                  return `Min Temp: ${value[0]}, Max Temp: ${value[1]}`;
                } else if (value[0]) {
                  return `Min Temp: ${value[0]}`;
                } else if (value[1]) {
                  return `Max Temp: ${value[1]}`;
                }
                return [];
              },
              update: (filterList, filterPos, index) => {
                console.log(
                  "customFilterListOnDelete: ",
                  filterList,
                  filterPos,
                  index
                );
    
                if (filterPos === 0) {
                  filterList[index].splice(filterPos, 1, "");
                } else if (filterPos === 1) {
                  filterList[index].splice(filterPos, 1);
                } else if (filterPos === -1) {
                  filterList[index] = [];
                }
    
                return filterList;
              }
            },
            filterOptions: {
              names: [],
              logic(value, filters) {
                const temperature = toNumber(
                  value.replace(TEMPERATURE_PREFIX, "")
                );
                const lower = toNumber(filters[0]);
                const upper = toNumber(filters[1]);
                if (lower && upper) {
                  return temperature < lower || temperature > upper;
                } else if (lower) {
                  return temperature < lower;
                } else if (upper) {
                  return temperature > upper;
                }
                return false;
              },
              display: (filterList, onChange, index, column) => (
                <div>
                  <FormLabel>Temperature</FormLabel>
                  <FormGroup row>
                    <TextField
                      label="min"
                      value={filterList[index][0] || ""}
                      onChange={event => {
                        filterList[index][0] = event.target.value;
                        onChange(filterList[index], index, column);
                      }}
                      style={{ width: "45%", marginRight: "5%" }}
                    />
                    <TextField
                      label="max"
                      value={filterList[index][1] || ""}
                      onChange={event => {
                        filterList[index][1] = event.target.value;
                        onChange(filterList[index], index, column);
                      }}
                      style={{ width: "45%" }}
                    />
                  </FormGroup>
                </div>
              )
            }
          }
        }
      ];
    
      const data = [
        { city: "Yonkers", temp: 3 },
        { city: "Hartford", temp: 11 },
        { city: "Tampa", temp: 25 },
        { city: "Dallas", temp: 30 }
      ];
    
      const options = {
        filterType: "checkbox"
      };
    
      return (
        <div className="App">
          <MUIDataTable
            title={"Employee List"}
            data={data}
            columns={columns}
            options={options}
          />
        </div>
      );
    }
    
    const rootElement = document.getElementById("root");
    ReactDOM.render(<App />, rootElement);
    

    (https://codesandbox.io/s/competent-kirch-uc01b)