useMuiTextFieldAdapter
Returns adapter props for building custom MUI TextField components with React Hook Form, with the field ref routed to the inner input.
useMuiTextFieldAdapter connects a field to React Hook Form and returns props you spread onto MUI's TextField. It's what MuiTextFieldElement uses internally.
Use the element for most forms. Reach for the hook when you want a reusable field with rules and messages built in, such as a username, a postcode or an amount field, that the rest of your app uses without repeating them.
Import
import { useMuiTextFieldAdapter } from "@piplup/rhf-adapters/mui-material";Usage
This UsernameField lowercases what you type and checks length and allowed characters with its own messages. Try ab, or a name with a space or a dash.
"use client";import type { ChangeEvent } from "react";import Stack from "@mui/material/Stack";import TextField from "@mui/material/TextField";import InputAdornment from "@mui/material/InputAdornment";import { MuiButtonElement, useMuiTextFieldAdapter,} from "@piplup/rhf-adapters/mui-material";import { type Control, useForm } from "react-hook-form";type FormValues = { username: string };// A username field that lowercases input as you type and enforces// length and character rules with its own messages.function UsernameField({ control }: { control: Control<FormValues> }) { const adapter = useMuiTextFieldAdapter< string, FormValues, "username", HTMLDivElement >({ control, name: "username", required: true, minLength: 3, maxLength: 20, pattern: /^[a-z0-9_]+$/, composeHelperText: true, helperText: "Letters, numbers and underscores", messages: { required: "Choose a username", minLength: "Use at least 3 characters", maxLength: "Use 20 characters or fewer", pattern: "Only letters, numbers and underscores are allowed", }, transform: { output: (event: ChangeEvent<HTMLInputElement>) => event.target.value.toLowerCase(), }, }); return ( <TextField {...adapter} label="Username" slotProps={{ input: { startAdornment: <InputAdornment position="start">@</InputAdornment>, }, }} /> );}export default function Page() { const { control, handleSubmit } = useForm<FormValues>({ defaultValues: { username: "" }, }); return ( <form noValidate onSubmit={handleSubmit((values) => alert(JSON.stringify(values, null, 2)), )} > <Stack direction="column" spacing={2}> <UsernameField control={control} /> <div> <MuiButtonElement control={control} variant="contained" type="submit"> Create account </MuiButtonElement> </div> </Stack> </form> );}How it works
Value handling, type: "number", transforms, and the required, min, max, minLength, maxLength and pattern rules with their default messages work exactly as described on useMuiInputBaseAdapter. This hook differs in how it handles refs and helper text.
Refs. React Hook Form's field ref is returned as inputRef, merged with any inputRef you pass to the hook. It lands on the inner <input>, so React Hook Form can focus the field when validation fails. The ref you pass as the hook's second argument is returned unchanged as ref and ends up on the TextField root <div>.
This is why the result can be spread straight onto TextField. With InputBase, Input, FilledInput or OutlinedInput you have to move ref to inputRef yourself.
Helper text. TextField accepts helperText and error, so both are rendered without extra markup. The element sets composeHelperText: true; the hook does not. Pass it yourself if you want the error message to replace your helperText while the field is invalid. Without it, only the red error state is applied.
Required. The returned required reaches TextField, which adds the asterisk to the label and the native required attribute to the input. Add noValidate to your <form> so the browser doesn't block submission before React Hook Form validates.
Types. Pass the root element type as the fourth generic, useMuiTextFieldAdapter<string, FormValues, "username", HTMLDivElement>(...), when TypeScript complains about ref. Without it, the returned ref is typed for unknown elements.
Pass type to the hook, not to TextField. The result always contains type, so <TextField type="password" {...adapter} /> ends up with type={undefined}. For type: "image" the result also contains src, which TextField doesn't accept. The element moves it to slotProps.htmlInput.
Props
Prop
Type
Return value
Prop
Type