Rxova
Skip to content

Time input — Migrating

Every migration here trades a popup for a typed field, so the honest first question is whether your users benefit from browsing times or from stating one. An appointment slot picker that only offers valid slots is a genuinely different control; a start time, an alarm or an opening hour is stated.

The second question is the value type: all three packages below hand you a Date, a moment, or a locale-formatted string. This hands you "14:30".

react-time-picker@rxova/react-time-inputNotes
value (Date | string)value (string | null)Always "HH:mm", 24-hour
onChange(value)onChange(value)Fires only for a complete, valid time
minTime / maxTimemin / maxInclusive, "HH:mm"
maxDetail="second"showSeconds
format="HH:mm"hour12The clock, not a format string; order comes from the locale
localelocaleBoth BCP 47
disableClockThere is no clock to disable
clockIcon, clearIconNo chrome to theme
amPmAriaLabel, hourAriaLabel, …segmentLabelsOne object
disabled, required, namesame
import { useState } from 'react'
import TimePicker from 'react-time-picker'
import 'react-time-picker/dist/TimePicker.css'
import 'react-clock/dist/Clock.css'
function Form() {
const [value, setValue] = useState<string | null>('10:00')
return <TimePicker onChange={setValue} value={value} disableClock />
}
import { useState } from 'react'
import { TimeInput } from '@rxova/react-time-input'
function Form() {
const [value, setValue] = useState<string | null>('10:00')
return <TimeInput label="Time" value={value} onChange={setValue} />
}

The value format is already compatible, so this is usually a drop-in. You lose the clock popup — which you were disabling anyway — and drop seven runtime dependencies and two stylesheet imports.

rc-time-picker is antd’s engine and hands you a moment object, so the value type changes.

rc-time-picker@rxova/react-time-inputNotes
value (moment)value (string)See the conversion below
onChange(moment)onChange(string)
showSecondshowSeconds
use12Hourshour12Defaults to the locale rather than to false
minuteStep / secondStepminuteStep / secondStepMust divide 60 here; invalid steps warn
formatOrder and clock come from locale
disabledHours, disabledMinutesmin / maxArbitrary per-unit disabling is not supported
open, popupClassName, …No popup
import moment from 'moment'
// moment -> value
const value = existing ? existing.format('HH:mm') : null
// value -> moment, when something downstream still needs one
const asMoment = value ? moment(value, 'HH:mm') : null

Note disabledHours/disabledMinutes have no equivalent: this component models a contiguous range, not an arbitrary set. If you need “every hour except 13:00”, validate on submit and pass invalid.

react-datetime is a combined date and time control. If you use both halves, pair this package with @rxova/react-date-input:

import { useState } from 'react'
import { DateInput } from '@rxova/react-date-input'
import { TimeInput } from '@rxova/react-time-input'
function When() {
const [date, setDate] = useState<string | null>(null)
const [time, setTime] = useState<string | null>(null)
// Compose at the boundary, where you know which timezone you mean.
const isoLocal = date && time ? `${date}T${time}` : null
return (
<>
<DateInput label="Date" value={date} onChange={setDate} />
<TimeInput label="Time" value={time} onChange={setTime} />
<input type="hidden" name="when" value={isoLocal ?? ''} />
</>
)
}

Two separate fields is also usually the better form design: it lets a user tab straight to the part they want to change, and it means each half validates on its own.

timeConstraints={{ minutes: { step: 15 } }} becomes minuteStep={15}.

The native control is good and free — keep it unless its appearance cannot be styled consistently across engines, or you need the 12/24-hour choice to follow an app-level locale rather than the OS one.

The value format is identical (HH:mm, 24-hour), so the swap is mechanical:

import { TimeInput } from '@rxova/react-time-input'
// Before
function Before() {
return <input type="time" name="at" min="09:00" max="17:00" />
}
// After
function After() {
return <TimeInput label="At" name="at" min="09:00" max="17:00" />
}

onChange gives you the string directly rather than event.target.value. Note one real difference: the native input enforces min/max through constraint validation, and this one reports the time and marks the field instead — see About.