Piplup
@piplup/rhf-adaptersmui-materialHooks

useMuiFormHelperTextAdapter

Returns props for an MUI FormHelperText that shows a React Hook Form field's error message without registering a value.

useMuiFormHelperTextAdapter returns props for MUI's FormHelperText that show the validation message of a field registered by another hook. It's what MuiFormHelperTextElement uses internally.

Use the hook when the message is part of a custom field component, or when you want control the element doesn't give you: your own markup for the message, a different place in the layout, or your own rule for when it renders.

Import

import { useMuiFormHelperTextAdapter } from "@piplup/rhf-adapters/mui-material";

Usage

PhoneField uses useMuiInputBaseAdapter for the input. PhoneError is a separate component that reads the same field's error, formats it with an errorParser, and renders nothing while the field is valid. Submit empty, then type letters, to see both messages.

"use client";import { useId } from "react";import Stack from "@mui/material/Stack";import FormHelperText from "@mui/material/FormHelperText";import InputBase from "@mui/material/InputBase";import Typography from "@mui/material/Typography";import {  MuiButtonElement,  useMuiFormHelperTextAdapter,  useMuiInputBaseAdapter,} from "@piplup/rhf-adapters/mui-material";import { type Control, type FieldError, useForm } from "react-hook-form";type FormValues = { phone: string };// An error-only helper text with its own formatting. It reads the field's// error by name; the input itself does not render any helper text.function PhoneError({ control, id }: { control: Control<FormValues>; id: string }) {  const helper = useMuiFormHelperTextAdapter<    FormValues,    "phone",    HTMLParagraphElement  >({    control,    name: "phone",    composeHelperText: true,    errorParser: (error) => {      const message = (error as FieldError | undefined)?.message;      return message ? <strong>{message}</strong> : null;    },  });  if (!helper.error) {    return null;  }  return <FormHelperText {...helper} id={id} role="alert" />;}function PhoneField({ control }: { control: Control<FormValues> }) {  const inputId = useId();  const errorId = `${inputId}-error`;  const { ref, helperText: _helperText, ...input } = useMuiInputBaseAdapter({    control,    name: "phone",    type: "tel",    required: true,    pattern: /^\+?[0-9 ]{7,15}$/,    messages: {      required: "Add a phone number so the courier can reach you",      pattern: "Use digits and spaces, with an optional leading +",    },  });  return (    <Stack spacing={0.5}>      <Typography component="label" htmlFor={inputId} variant="body2">        Phone for delivery      </Typography>      <InputBase        {...input}        id={inputId}        inputRef={ref}        slotProps={{ input: { "aria-describedby": errorId } }}        sx={{          px: 1.5,          py: 0.5,          border: 1,          borderRadius: 1,          borderColor: input.error ? "error.main" : "divider",        }}      />      <PhoneError control={control} id={errorId} />    </Stack>  );}export default function Page() {  const { control, handleSubmit } = useForm<FormValues>({    defaultValues: { phone: "" },  });  return (    <form      noValidate      onSubmit={handleSubmit((values) =>        alert(JSON.stringify(values, null, 2)),      )}    >      <Stack direction="column" spacing={2}>        <PhoneField control={control} />        <div>          <MuiButtonElement control={control} variant="contained" type="submit">            Continue          </MuiButtonElement>        </div>      </Stack>    </form>  );}

How it works

The hook reads the field's state by name and registers nothing, like useMuiFormLabelAdapter. It returns children, error, disabled, className, style, classes and ref, all of which FormHelperText accepts, so the result can be spread as is.

Children and the error message. You pass the normal text as children. What comes back as children depends on composeHelperText:

composeHelperTextField validField invalid
not set (default)your childrenyour children (only error changes)
trueyour childrenthe error message

The element always sets composeHelperText: true; the hook doesn't. If the error has no message, your children are shown.

Showing it only on error. The element's renderOnError prop isn't part of the hook. Check error yourself and return null, as PhoneError does.

Formatting the message. By default the message is error.message when it's a string. Pass errorParser to return any React node instead. It receives the field's error object, or undefined when there is none.

A parser from FormErrorProvider takes priority over the errorParser you pass to this hook. Inside a provider, your per-field parser is ignored.

Accessibility. The hook doesn't link the message to the input. Give FormHelperText an id and point the input's aria-describedby at it, as the demo does.

Props

Prop

Type

Return value

Prop

Type

On this page