javascriptreactjsdebuggingag-gridjquerydatetimepicker

Datetime filter for AG-Grid - Clear input text on button click


Based on this proposed solution in the AG-Grid Github issues, I am trying to implement a jQuery DateTime Picker as a filter in my React/AG_Grid project.

I currently have my table set up so that I can clear the filters that have been applied to my table with a button click. The desired behavior is that once the Reset Filters button is clicked, the filters AND the text inside of the filter input should be cleared. As it is set up now, the filters are being cleared from the table as desired but when I reopen the filter input, the text from the previous filter is still there.


I have a Code Sandbox set up here with a simplified version of my current setup.

Steps to recreate:

const App = () => {
  const [gridApi, setGridApi] = useState([]);
  const [gridColumnApi, setGridColumnApi] = useState([]);
  const [rowData, setRowData] = useState([]);

  useEffect(() => {
    const formattedDates = dataSet.map((data) => {
      return {
        id: data.id,
        eventTimestamp: new Date(data.eventTimestamp)
      };
    });
    setRowData(formattedDates);
  }, []);

  // ***************************************************
  // The Handle Click logic for the reset filters button:
  // ***************************************************
  const resetAppliedFilters = () => {
    gridApi.setFilterModel(null);
    CustomDateComponent.prototype.setDate(null);
  };

  const cols = [
    {
      field: "id",
      headerName: "ID",
      minWidth: 100,
      maxWidth: 150
    },
    {
      field: "eventTimestamp",
      headerName: "Event Timestamp",
      minWidth: 225,
      filter: "agDateColumnFilter",
      filterParams: {
        defaultOption: "inRange",
        // ***************************************************
        // Comparator function for datetime picker:
        // ***************************************************
        comparator: function (filterLocalDate, cellValue) {
          filterLocalDate.setMilliseconds(0);
          cellValue.setMilliseconds(0);
          let filterBy = filterLocalDate.getTime();
          let filterMe = cellValue.getTime();
          if (filterBy === filterMe) {
            return 0;
          }

          if (filterMe < filterBy) {
            return -1;
          }

          if (filterMe > filterBy) {
            return 1;
          }
        }
      }
    }
  ];

  const onGridReady = (params) => {
    setGridApi(params.api);
    setGridColumnApi(params.columnApi);
    // ***************************************************
    // Table event listener:
    // ***************************************************
    params.api.addGlobalListener((type, event) => {
      switch (type) {
        case "filterChanged":
          console.log(event);
          return;
        default:
          return null;
      }
    });
  };

  return (
    <div className="App">
      <Button onClick={resetAppliedFilters} variant="outlined">
        Reset Filters
      </Button>
      <hr />
      <div
        className={"ag-theme-balham"}
        style={{ height: "86vh", width: "100%" }}
      >
        <AgGridReact
          onGridReady={onGridReady}
          rowData={rowData}
          rowSelection="multiple"
          defaultColDef={{
            flex: 1,
            minWidth: 100,
            resizable: true,
            sortable: true,
            filter: true
          }}
          pagination
          columnDefs={cols}
          components={{
            agDateInput: CustomDateComponent
          }}
        />
      </div>
    </div>
  );
};

// ***************************************************
// Custom datetime picker component:
// ***************************************************
function CustomDateComponent() {}

CustomDateComponent.prototype.init = function (params) {
  this.params = params;
  this.eGui = document.createElement("div");
  this.eInput = document.createElement("input");
  this.eGui.appendChild(this.eInput);
  jQuery(this.eInput).datetimepicker({
    mask: true, // '9999/19/39 29:59' - digit is the maximum possible for a cell
    onChangeDateTime: this.onDateChanged.bind(this)
  });
};

CustomDateComponent.prototype.onDateChanged = function (currentDateTime) {
  this.date = currentDateTime;
  this.params.onDateChanged();
};

CustomDateComponent.prototype.getGui = function () {
  return this.eGui;
};

CustomDateComponent.prototype.getDate = function () {
  return this.date;
};

CustomDateComponent.prototype.setDate = function (date) {
  this.date = date;
};

CustomDateComponent.prototype.destroy = function () {
  jQuery(this.eInput).datetimepicker("destroy");
};

export default App;

If anyone can help out or point me in the right direction, it would be greatly appreciated. TIA!


Solution

  • While I was trying to figure this out on my own, I came across this Stack Overflow article that explains why you should NOT use React and jQuery together. This is great advice since you can see in the above example, the state wasn't being managed properly among other issues.

    After further reading of the AG-Grid docs' custom date component section and this post on AG-Grid's blog, I was able to implement a solution that uses react-datetime-picker as the custom filter component. You then have to pass it to the table's frameworkComponents prop.

    LIVE DEMO ON STACK BLITZ

    DTPicker.jsx

    import DateTimePicker from "react-datetime-picker";
    
    export default forwardRef((props, ref) => {
      const [selectedDate, setSelectedDate] = useState(null);
    
      function handleDateChange(d) {
        if (d) {
          d = new Date(d);
          setSelectedDate(d);
        } else {
          setSelectedDate(null);
        }
      }
    
      // props.onDateChanged must be called after updating our component's internal state:
      useEffect(props.onDateChanged, [selectedDate]);
    
      // getDate and setDate are required by AG-Grid to sync ag-Grid's date 
      // filter value with that of our components:
      useImperativeHandle(ref, () => {
        return {
          getDate: () => {
            return selectedDate;
          },
          setDate: d => {
            handleDateChange(d);
          }
        };
      });
    
      return (
        <>
          <DateTimePicker
            onChange={handleDateChange}
            value={selectedDate}
            maxDetail="second"
            disableCalendar={true}
            disableClock={true}
          />
        </>
      );
    });
    

    App.jsx

    import DTPicker from './DTPicker'
    
    // Add your custom filter logic in your column:
    const cols = [
        ...,
        {
          field: "eventTimestamp",
          headerName: "Event Timestamp",
          filter: "agDateColumnFilter",
          filterParams: {
            defaultOption: "inRange",
            comparator: function(filterLocalDate, cellValue) {
              let filterBy = filterLocalDate.getTime();
              let filterMe = cellValue.getTime();
              if (filterBy === filterMe) {
                return 0;
              }
    
              if (filterMe < filterBy) {
                return -1;
              }
    
              if (filterMe > filterBy) {
                return 1;
              }
            }
          }
        },
        ...
      ];
    
    <AgGridReact
        ...
        frameworkComponents={{
            agDateInput: DTPicker
        }}
        ...
    />