Piplup
@piplup/rhf-adaptersreact-number-formatHooks

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.

MM/YY

"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:

PropChecksDefault message
requiredvalue is not emptyPlease fill out this field.
minLength / maxLengthlength of the stored stringPlease lengthen this text to n characters or more / Please shorten this text to n characters or less.
patternstored string matchesPlease match the requested format.
min / maxstored value as a numberValue 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

On this page