React Data GridMoment.js date Cell Type - Step-by-Step Guide

Overview

This guide shows how to create a custom time cell type using the Moment.js (opens new window) library. Users can format times using the Moment.js API.

Difficulty: Beginner Time: ~15 minutes Libraries: moment

Complete Example

    What You'll Build

    A cell that:

    • Displays time values as formatted text
    • Accepts timeFormat options for customization (e.g., HH:mm, h:mm:ss a)
    • Validates time input using Moment.js
    • Auto-corrects time format when correctFormat is enabled

    Prerequisites

    npm install moment
    

    Step 1: Import Dependencies

    import Handsontable from 'handsontable/base';
    import { registerAllModules } from 'handsontable/registry';
    import { getRenderer } from 'handsontable/renderers';
    import { getEditor } from 'handsontable/editors';
    import { registerCellType } from 'handsontable/cellTypes';
    import moment from 'moment';
    
    registerAllModules();
    

    Why this matters:

    • moment handles time parsing, validation, and formatting
    • getRenderer('text') and getEditor('text') reuse Handsontable's built-in text renderer and editor
    • registerCellType registers the custom cell type for use in column config

    Step 2: Create the Renderer

    We reuse the built-in text renderer, which displays the time value as plain text:

    renderer: getRenderer('text')
    

    Step 3: Create the Validator

    The validator parses the input using Moment.js and checks it against the configured timeFormat. It handles Unix timestamps, two-digit shorthand (e.g., 9 becomes 9:00), and auto-correction:

    validator: function(value, callback) {
      const timeFormat = this.timeFormat ?? 'h:mm:ss a';
      let valid = true;
    
      if (value === null) {
        value = '';
      }
    
      value = /^\d{3,}$/.test(value) ? parseInt(value, 10) : value;
    
      const twoDigitValue = /^\d{1,2}$/.test(value);
    
      if (twoDigitValue) {
        value += ':00';
      }
    
      const date = moment(value, [
        'YYYY-MM-DDTHH:mm:ss.SSSZ',
        'X', // Unix timestamp
        'x' // Unix ms timestamp
      ], true).isValid() ?
        moment(value) : moment(value, timeFormat);
      let isValidTime = date.isValid();
    
      // is it in the specified format
      let isValidFormat = moment(value, timeFormat, true).isValid() && !twoDigitValue;
    
      if (this.allowEmpty && value === '') {
        isValidTime = true;
        isValidFormat = true;
      }
      if (!isValidTime) {
        valid = false;
      }
      if (!isValidTime && isValidFormat) {
        valid = true;
      }
      if (isValidTime && !isValidFormat) {
        if (this.correctFormat === true) {
          const correctedValue = date.format(timeFormat);
    
          this.instance.setDataAtCell(this.visualRow, this.visualCol, correctedValue, 'timeValidator');
          valid = true;
        } else {
          valid = false;
        }
      }
    
      callback(valid);
    }
    

    What's happening:

    • Converts numeric-only input (3+ digits) to integers for Unix timestamp parsing
    • Appends :00 to 1-2 digit values (e.g., 9 becomes 9:00)
    • Tries ISO 8601 and Unix timestamp formats first, then falls back to the configured timeFormat
    • If correctFormat is enabled, auto-corrects valid but misformatted times

    Step 4: Create the Editor

    We reuse the built-in text editor — a simple text input for editing the time value:

    editor: getEditor('text')
    

    Step 5: Complete Cell Type Definition

    Put all the pieces together and register the cell type:

    const cellTimeTypeDefinition = {
      renderer: getRenderer('text'),
      validator: function(value, callback) {
        // ... validator code from Step 3
      },
      editor: getEditor('text'),
    };
    
    registerCellType('moment-time', cellTimeTypeDefinition);
    

    What's happening:

    • renderer: Uses the built-in text renderer to display the time value
    • validator: Custom validator that validates and optionally corrects time format using Moment.js
    • editor: Uses the built-in text editor for simple text input
    • registerCellType: Registers the moment-time cell type for use in column config

    Step 6: Use in Handsontable

    registerCellType('moment-time', cellTimeTypeDefinition);
    
    const hotOptions: Handsontable.GridSettings = {
      data,
      colHeaders: ['Item Name', 'Category', 'Lead Engineer', 'Arrival Time', 'Cost'],
      autoRowSize: true,
      rowHeaders: true,
      height: 'auto',
      width: '100%',
      autoWrapRow: true,
      headerClassName: 'htLeft',
      columns: [
        { data: 'itemName', type: 'text', width: 130 },
        { data: 'category', type: 'text', width: 120 },
        { data: 'leadEngineer', type: 'text', width: 150 },
        {
          data: 'time',
          type: 'moment-time',
          width: 150,
          timeFormat: 'HH:mm',
          correctFormat: true,
        },
        {
          data: 'cost',
          type: 'numeric',
          width: 120,
          className: 'htRight',
          numericFormat: {
            pattern: '$0,0.00',
            culture: 'en-US',
          },
        },
      ],
      licenseKey: 'non-commercial-and-evaluation',
    };
    
    const hot = new Handsontable(container, hotOptions);
    

    Key configuration:

    • type: 'moment-time' - uses the custom cell type on the Arrival Time column
    • timeFormat: 'HH:mm' - the Moment.js format string for 24-hour time
    • correctFormat: true - automatically reformats valid times to the expected format
    • headerClassName: 'htLeft' - left-aligns all column headers

    How It Works - Complete Flow

    1. Initial Render: Cell displays the time value as plain text using the text renderer
    2. User clicks cell: The built-in text editor opens for editing
    3. User enters time: Input like 9, 14:30, or a Unix timestamp is accepted
    4. Validation: Moment.js checks the format and time validity; auto-corrects if correctFormat is enabled
    5. Save: Valid values are saved to the cell; invalid values are rejected