coreui / coreui/coreui-react

DatePicker should support onBlur validation

Open
#482 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
715
Forks
276
Avg merge
1d 14h
Merged PRs (30d)
8

Description

When a user types a date into the field, and then onBlur's the element, it should show that the date they have entered is invalid, or if it parses, show it as a valid date.

  How CDateRangePicker handles typed input today

  The key function is handleOnChange (line 153 of the ESM build) which is wrapped in useDebouncedCallback with inputOnChangeDelay (default 750ms). It
  fires on every keystroke in the <input> onChange handler (line 201-202):

  onChange: (event) => {
      handleOnChange(event.target.value, type);
  }

  The debounced callback:
  1. Parses the typed value via inputDateParse (custom), getLocalDateFromString (day selection), or convertToDateObject
  2. If the parsed date is disabled, it bails
  3. If valid, updates calendar date + input display + calls onStartDateChange/onEndDateChange
  4. If invalid (null parse), it still calls setDate(null) and onChange(null) — this is what clears the date on bad input

  The problem

  There's no onBlur handler on the input at all (line 195-203). So if a user types garbage and the debounce fires, it immediately nulls out the date.
  There's no chance to "revert to last good value" on blur, and no blur-time validation.

  How to patch it

  You need to add a onBlur handler to the <input> in renderInput that validates the current input value and reverts to the last known good date if it's
   invalid. Here's the patch for the ESM build:

  diff --git a/dist/esm/components/date-range-picker/CDateRangePicker.js b/dist/esm/components/date-range-picker/CDateRangePicker.js
  --- a/dist/esm/components/date-range-picker/CDateRangePicker.js
  +++ b/dist/esm/components/date-range-picker/CDateRangePicker.js
  @@ -185,6 +185,30 @@
       }, inputOnChangeDelay);
  +    const handleInputBlur = (value, input) => {
  +        // Cancel any pending debounced onChange — we will handle it now.
  +        // (useDebouncedCallback stores its timer on timeout.current but
  +        //  we don't have access here, so we parse and validate directly.)
  +        const isStartInput = input === 'start';
  +        const currentDate = isStartInput ? _startDate : _endDate;
  +        const inputRef = isStartInput ? inputStartRef : inputEndRef;
  +
  +        if (!value || value.trim() === '') {
  +            // User cleared the field — treat as null
  +            const setDate = isStartInput ? setStartDate : setEndDate;
  +            const onChange = isStartInput ? onStartDateChange : onEndDateChange;
  +            setDate(null);
  +            onChange === null || onChange === void 0 ? void 0 : onChange(null);
  +            return;
  +        }
  +
  +        let date = null;
  +        if (inputDateParse) {
  +            date = inputDateParse(value);
  +        } else if (selectionType === 'day') {
  +            date = getLocalDateFromString(value, locale, timepicker);
  +        } else {
  +            date = convertToDateObject(value, selectionType);
  +        }
  +
  +        // If the parsed date is invalid or disabled, revert to last good value
  +        if (!date || (date instanceof Date && Number.isNaN(date.getTime())) ||
  +            (date instanceof Date && isDateDisabled(date,
  +                _minDate ? convertToDateObject(_minDate, selectionType) : null,
  +                _maxDate ? convertToDateObject(_maxDate, selectionType) : null,
  +                disabledDates))) {
  +            setInputValue(inputRef.current, currentDate);
  +        }
  +    };
       const renderInput = (type) => {
           const isStart = type === 'start';
           const hoverValue = isStart ? inputStartHoverValue : inputEndHoverValue;
  @@ -198,7 +222,10 @@
           }), { placeholder: placeholderText, readOnly: inputReadOnly, required: required, onChange: (event) => {
                   handleOnChange(event.target.value, type);
  -            }, onClick: () => setSelectEndDate(!isStart), ref: inputRef })));
  +            }, onBlur: (event) => {
  +                handleInputBlur(event.target.value, type);
  +            }, onClick: () => setSelectEndDate(!isStart), ref: inputRef })));
           if (previewDateOnHover && !disabled) {

  The same change would need to be mirrored in the CJS build at dist/cjs/components/date-range-picker/CDateRangePicker.js.

  What this does

  1. On blur, it parses the current input value using the same logic as handleOnChange
  2. If the value is empty, it nulls out the date (user intentionally cleared it)
  3. If the value doesn't parse to a valid date, or parses to a disabled date, it reverts the input display back to the last known good
  _startDate/_endDate via setInputValue
  4. Valid values are already handled by the debounced onChange that fires before blur

  Additional consideration

  If you also want to prevent the debounced onChange from firing null on invalid input (so it only validates on blur, not mid-typing), you'd also
  change the handleOnChange callback to bail early when the parsed date is null instead of propagating null:

  -        // Update state and input
  -        setDate(formatedDate);
  -        onChange === null || onChange === void 0 ? void 0 : onChange(formatedDate);
  +        // Only update state for valid dates — blur handler deals with invalid input
  +        if (formatedDate !== null) {
  +            setDate(formatedDate);
  +            onChange === null || onChange === void 0 ? void 0 : onChange(formatedDate);
  +        }

  This way the debounce still updates the calendar/input for valid dates as you type, but doesn't null out the value for partial/invalid input — that
  decision is deferred to blur.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in dist/esm/components/date-range-picker/CDateRangePicker.js at handleOnChange and renderInput, then compare the corresponding CJS file. Trace the existing date parsing and debounced update flow before adding blur validation; done means invalid or disabled input reverts to the last valid date while cleared and valid values retain the described behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
react, typescript
Domain
frontend
Issue type
Feature
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.