React Hook Form Integration
Learn how to use NumoraInput with react-hook-form using the recommended Controller pattern.
Overview
NumoraInput manages its own DOM value directly (via an uncontrolled defaultValue internally), but supports a controlled-style value prop that syncs programmatic changes into the input. React Hook Form's Controller component is the recommended integration pattern - it handles the value, onChange, onBlur, ref, and disabled wiring automatically.
Note: numora-react does not require react-hook-form as a dependency. It works with react-hook-form when it's present in your project.
Controller Pattern (recommended)
Always forward all four field properties - onChange, onBlur, ref, and disabled. Omitting onBlur breaks touched-state tracking; omitting ref breaks auto-focus on validation errors.
Basic form with Controller pattern - works with setValue() automatically
import { useForm, Controller } from 'react-hook-form'
import { NumoraInput } from 'numora-react'
function Form() {
const { control, handleSubmit, setValue } = useForm()
return (
<form onSubmit={handleSubmit((data) => console.log(data))}>
<Controller
control={control}
name="amount"
render={({ field: { onChange, onBlur, value, ref, disabled } }) => (
<NumoraInput
ref={ref}
name="amount"
value={value || ''}
onChange={onChange}
onBlur={onBlur}
disabled={disabled}
maxDecimals={2}
thousandSeparator=","
/>
)}
/>
<button type="button" onClick={() => setValue('amount', '1000')}>
Set to 1000
</button>
<button type="submit">Submit</button>
</form>
)
}Validation errors
Use fieldState.error from the render prop to display validation messages:
<Controller
control={control}
name="amount"
rules={{ required: 'Amount is required', min: { value: 1, message: 'Must be at least 1' } }}
render={({ field: { onChange, onBlur, value, ref, disabled }, fieldState: { error } }) => (
<>
<NumoraInput
ref={ref}
value={value || ''}
onChange={onChange}
onBlur={onBlur}
disabled={disabled}
maxDecimals={2}
thousandSeparator=","
/>
{error && <span>{error.message}</span>}
</>
)}
/>💡 Tip: NumoraInput's onChange always exposes the raw (unformatted) numeric string - no post-processing required:
e.target.value- raw numeric string (e.g."1000.50") - separators strippede.target.formattedValue- formatted display string (e.g."1,000.50"), typed viaNumoraHTMLInputElement
Complete example
A form with programmatic updates (Max / Half buttons) using the Controller pattern:
import { useForm, Controller } from 'react-hook-form'
import { NumoraInput } from 'numora-react'
interface FormValues {
amount: string
}
function SwapForm() {
const { control, handleSubmit, setValue, watch } = useForm<FormValues>({
defaultValues: { amount: '' }
})
const handleMaxClick = () => {
setValue('amount', '1000000')
}
const handleHalfClick = () => {
const current = parseFloat(watch('amount') || '0')
setValue('amount', (current / 2).toString())
}
return (
<form onSubmit={handleSubmit((data) => console.log(data))}>
<Controller
control={control}
name="amount"
rules={{ required: 'Amount is required' }}
render={({ field: { onChange, onBlur, value, ref, disabled }, fieldState: { error } }) => (
<>
<NumoraInput
ref={ref}
name="amount"
value={value || ''}
onChange={onChange}
onBlur={onBlur}
disabled={disabled}
maxDecimals={6}
thousandSeparator=","
placeholder="0.0"
/>
{error && <span>{error.message}</span>}
</>
)}
/>
<div>
<button type="button" onClick={handleMaxClick}>Max</button>
<button type="button" onClick={handleHalfClick}>Half</button>
</div>
<button type="submit">Submit</button>
</form>
)
}Register pattern (uncontrolled)
For basic forms that don't need programmatic updates or setValue(), you can use the register pattern. Spread the register result and leave NumoraInput to manage its own value - do not also pass a value prop:
import { useForm } from 'react-hook-form'
import { NumoraInput } from 'numora-react'
function Form() {
const { register, handleSubmit } = useForm()
return (
<form onSubmit={handleSubmit((data) => console.log(data))}>
<NumoraInput
{...register('amount')}
maxDecimals={2}
thousandSeparator=","
/>
<button type="submit">Submit</button>
</form>
)
}⚠️ Limitation: With the register pattern, calling setValue() won't update the displayed value. Use the Controller pattern whenever you need programmatic updates.
Key points
- Always forward
onBlur,ref, anddisabledfrom the Controller field - these are required for touched-state tracking, focus management on errors, and disabled-field support. - Controller pattern (recommended): works with
setValue(), validation, and all react-hook-form features. - Register pattern: works for basic form submission only -
setValue()won't update the UI. - Raw values by default:
e.target.valueinonChangealways returns the raw numeric string (separators stripped).field.onChange(e.target.value)stores the clean value - no extra handling needed. The formatted display string is available ase.target.formattedValueviaNumoraHTMLInputElement. - No extra dependencies:
numora-reactdoesn't require react-hook-form.