Search code examples
javascriptreactjsdatetimeag-gridflatpickr

React AG-Grid Time Stamps in DateTime Picker not matching with data values / AG-Grid datetime filter


DEMO ON STACK BLITZ

I'm using AG-Grid, a data table package, in a React project of mine. This table holds a set of DateTime values. In order to filter the DateTime values in the table, I am using a slightly modified version of the example DateTime filter provided by the AG-Grid documentation, which uses the Flatpickr package.

The DateTime values in my table are in ISO string format. When I make a selection via the calendar and clock, the values in the filter inputs are not the same as what I am selecting on the calendar and they do not match the values in the table cells.

Here is a screenshot showing the difference between the calendar selection and the input: Picture of undesired behavior

And here is a screenshot of the results of the applied filters and how they do not match the inputs: Undesired behavior after results are applied

I've been stumped by this problem for a long time, so if anyone can help me figure out why the values in the inputs, calendar and filter results are not matching up, it would be a great help.

DEMO ON STACK BLITZ


DataGrid.jsx (AG-Grid Docs for reference)

import React, { useState } from 'react';
import { AgGridColumn, AgGridReact } from 'ag-grid-react';
import DateTimePicker from './DateTimePicker';
import DATES from './data/DATES.json';
import 'ag-grid-community/dist/styles/ag-grid.css';
import 'ag-grid-community/dist/styles/ag-theme-alpine.css';

const DataGrid = () => {
  const [rowData, setRowData] = useState(DATES);

  const COLUMN_DEFINITION = [
    {
      field: 'dateTime',
      headerName: 'Date Time',
      filter: 'agDateColumnFilter',
      filterParams: {
        defaultOption: 'inRange',
        // Filtering function for DateTime values:
        comparator: function(filterLocalDate, cellValue) {
          filterLocalDate = new Date(filterLocalDate);
          cellValue = new Date(cellValue);
          let filterBy = filterLocalDate.getTime();
          let filterMe = cellValue.getTime();
          if (filterBy === filterMe) {
            return 0;
          }

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

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

  return (
    <div className="ag-theme-alpine" style={{ height: 400, width: 600 }}>
      <AgGridReact
        columnDefs={COLUMN_DEFINITION}
        rowData={rowData}
        defaultColDef={{
          sortable: true,
          filter: true,
          filterParams: {
            buttons: ['apply', 'clear', 'reset', 'cancel']
          }
        }}
        frameworkComponents={{
          agDateInput: DateTimePicker
        }}
      />
    </div>
  );
};

export default DataGrid;

DateTimePicker.jsx (Flatpickr Docs for reference):

import React, {
  useEffect,
  useState,
  useRef,
  forwardRef,
  useImperativeHandle
} from 'react';
import flatpickr from 'flatpickr';
import 'flatpickr/dist/flatpickr.min.css';

export default forwardRef((props, ref) => {
  const [date, setDate] = useState(null);
  const [picker, setPicker] = useState(null);
  const refFlatPickr = useRef();
  const refInput = useRef();

  const onDateChanged = selectedDates => {
    const [selectedDate] = selectedDates;
    setDate(selectedDate);
    props.onDateChanged();
  };

  useEffect(() => {
    setPicker(
      flatpickr(refFlatPickr.current, {
        onChange: onDateChanged,
        dateFormat: 'Z',
        wrap: true,
        enableTime: true,
        enableSeconds: true,
        time_24hr: true
      })
    );
  }, []);

  useEffect(() => {
    if (picker) {
      picker.calendarContainer.classList.add('ag-custom-component-popup');
    }
  }, [picker]);

  useEffect(() => {
    if (picker) {
      picker.setDate(date);
    }
  }, [date, picker]);

  useImperativeHandle(ref, () => ({
    getDate() {
      return date;
    },

    setDate(date) {
      setDate(date);
    },

    setInputPlaceholder(placeholder) {
      if (refInput.current) {
        refInput.current.setAttribute('placeholder', placeholder);
      }
    },

    setInputAriaLabel(label) {
      if (refInput.current) {
        refInput.current.setAttribute('aria-label', label);
      }
    }
  }));

  return (
    <div
      className="ag-input-wrapper custom-date-filter"
      role="presentation"
      ref={refFlatPickr}
    >
      <input type="text" ref={refInput} data-input style={{ width: '100%' }} />
      <a class="input-button" title="clear" data-clear>
        <i class="fa fa-times" />
      </a>
    </div>
  );
});

Solution

  • I made two changes to your example:

    1. commented out the property dateFormat: 'Z' inside the date picker. This is causing the selected date and the date inside the input box of the date picker to have a different format.
    2. inside filterParams.comparator I've removed the 'Z' appended to the end of the cellValue string. It seems that when calling new Date(cellValue) the date is not giving the correct hour value as the cellValue string. So to fix this, I simply just removed 'Z':
    cellValue = new Date(cellValue.slice(0, -1));
    

    See this implemented here: https://stackblitz.com/edit/react-6dwqvh?file=src%2FDataGrid.jsx