useNumberFormatBaseAdapter
Returns adapter props for wiring react-number-format's NumberFormatBase, with your own format function, to React Hook Form.
useNumberFormatBaseAdapter connects a field to React Hook Form and returns props you spread onto NumberFormatBase from react-number-format. It's what NumberFormatBaseElement uses internally, and useNumericFormatAdapter and usePatternFormatAdapter are thin wrappers around it.
Use the element for one-off fields. Reach for the hook when you write your own format function (card expiry, time, custom IDs) and want a reusable field with that format, its rules and its TextField styling baked in.
Import
import { useNumberFormatBaseAdapter } from "@piplup/rhf-adapters/react-number-format";Usage
This CardExpiryField formats digits as MM/YY, renders a TextField, and rejects incomplete or past dates. Type 0120 and submit to see the expiry rule; the alert shows what the form stores.
"use client";import Stack from "@mui/material/Stack";import TextField from "@mui/material/TextField";import { MuiButtonElement } from "@piplup/rhf-adapters/mui-material";import { useNumberFormatBaseAdapter } from "@piplup/rhf-adapters/react-number-format";import { type Control, useForm } from "react-hook-form";import { NumberFormatBase } from "react-number-format";type FormValues = { expiry: string };// Turns the typed digits into "MM/YY" and keeps the month between 01 and 12.function formatExpiry(digits: string) { if (digits === "") { return ""; } let month = digits.slice(0, 2); const year = digits.slice(2, 4); if (month.length === 1 && Number(month) > 1) { month = `0${month}`; } else if (month === "00") { month = "01"; } else if (Number(month) > 12) { month = "12"; } return month.length < 2 ? month : `${month}/${year}`;}// The form stores the digits only, e.g. "0428" for "04/28".function isExpired(digits: string) { const month = Number(digits.slice(0, 2)); const year = 2000 + Number(digits.slice(2, 4)); const now = new Date(); return year < now.getFullYear() || (year === now.getFullYear() && month < now.getMonth() + 1);}function CardExpiryField({ control }: { control: Control<FormValues> }) { const adapter = useNumberFormatBaseAdapter<string, FormValues, "expiry">({ control, name: "expiry", required: true, composeHelperText: true, helperText: "MM/YY", messages: { required: "Enter the card's expiry date" }, rules: { validate: { complete: (value) => value.length === 4 || "Enter both month and year", notExpired: (value) => !isExpired(value) || "This card has expired", }, }, }); return ( <NumberFormatBase customInput={TextField} label="Expiry date" format={formatExpiry} {...adapter} /> );}export default function Page() { const { control, handleSubmit } = useForm<FormValues>({ defaultValues: { expiry: "" }, }); return ( <form noValidate onSubmit={handleSubmit((values) => alert(JSON.stringify(values, null, 2)))} > <Stack direction="column" spacing={2}> <CardExpiryField control={control} /> <div> <MuiButtonElement control={control} variant="contained" type="submit"> Save card </MuiButtonElement> </div> </Stack> </form> );}How it works
What gets stored. The hook listens to onValueChange, not onChange, and writes values.value to the form: the unformatted string that removeFormatting returns. NumberFormatBase's default removeFormatting keeps digits only, so typing 04/28 stores "0428". Pass your own removeFormatting if separators should survive.
To store something else, pass transform.output. It receives the same arguments as onValueChange (values, sourceInfo) plus the current value, so (values) => values.formattedValue stores "04/28" instead.
What gets displayed. The form value is passed back as value, with undefined turned into "" so the input stays controlled. NumberFormatBase treats a string value as already formatted: it runs removeFormatting on it, then format. A number value goes straight to format. If you store something your removeFormatting can't read back, also pass transform.input.
Validation. All rules apply to the stored value, not to what the user sees:
| Prop | Checks | Default message |
|---|---|---|
required | value is not empty | Please fill out this field. |
minLength / maxLength | length of the stored string | Please lengthen this text to n characters or more / Please shorten this text to n characters or less. |
pattern | stored string matches | Please match the requested format. |
min / max | stored value as a number | Value must be n or later. / Value must be n or earlier. |
Override any of them through messages. For checks that span parts of the value, like the expiry date above, use rules.validate.
Errors and helper text. error and helperText are only returned when you set composeHelperText: true. Then helperText is the error message when the field is invalid and your helperText otherwise. Without it, neither prop is returned, because the default NumberFormatBase renders a plain <input>, which doesn't accept them.
Ref and blur. The returned getInputRef is React Hook Form's field ref merged with any getInputRef you pass. onBlur marks the field as touched and then calls your onBlur.
With customInput={TextField}, react-number-format attaches getInputRef to the TextField root, not the <input>. React Hook Form then can't move focus to this field when validation fails.
Props
Prop
Type
Return value
Prop
Type