Piplup
@piplup/rhf-adaptersmui-materialHooks

useMuiRadioAdapter

Returns props that connect a single MUI Radio to React Hook Form, for radio layouts that don't use RadioGroup.

useMuiRadioAdapter connects one MUI Radio to a React Hook Form field and returns props you spread onto it. It's what MuiRadioElement uses internally.

Each radio is wired to the field on its own, so the options can live anywhere: in a grid of cards, in table rows, or spread across a layout. If your options sit together in an MUI RadioGroup, use useMuiRadioGroupAdapter instead, which registers the field once for the whole group. Use the hook rather than the element when you want one component that renders an option with its label and logic.

Import

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

Usage

Each SeatOption stores a number, not the string the DOM returns. The options don't set required themselves; they inherit it from the surrounding FormControl. Submit without choosing to see the error.

Seats
"use client";import FormControl from "@mui/material/FormControl";import FormControlLabel from "@mui/material/FormControlLabel";import FormGroup from "@mui/material/FormGroup";import FormHelperText from "@mui/material/FormHelperText";import FormLabel from "@mui/material/FormLabel";import Radio from "@mui/material/Radio";import Stack from "@mui/material/Stack";import {  MuiButtonElement,  useMuiRadioAdapter,} from "@piplup/rhf-adapters/mui-material";import type * as React from "react";import { type Control, useForm, useFormState } from "react-hook-form";type FormValues = { seats: number | null };const SEAT_OPTIONS = [1, 2, 4];// One radio. It inherits `required` from the surrounding FormControl, and// stores a number instead of the string the DOM gives back.function SeatOption({  control,  seats,}: {  control: Control<FormValues>;  seats: number;}) {  // Radio has no error or helperText props, so keep them off the DOM.  // `required` is dropped too, or FormControlLabel would put an asterisk on  // every option. The legend already shows one.  const {    error: _error,    helperText: _helperText,    required: _required,    ...adapter  } = useMuiRadioAdapter<unknown, FormValues, "seats", HTMLButtonElement>({    control,    name: "seats",    value: seats,    messages: { required: "Choose how many seats you need" },    transform: {      output: (event: React.ChangeEvent<HTMLInputElement>) =>        Number(event.target.value),    },  });  return (    <FormControlLabel      control={<Radio {...adapter} />}      label={seats === 1 ? "1 seat" : `${seats} seats`}    />  );}function SeatsField({ control }: { control: Control<FormValues> }) {  const { errors } = useFormState({ control, name: "seats" });  const message = errors.seats?.message;  return (    <FormControl required error={!!message} component="fieldset">      <FormLabel component="legend">Seats</FormLabel>      {/* A FormGroup, not a RadioGroup: each radio is wired to the form itself. */}      <FormGroup row>        {SEAT_OPTIONS.map((seats) => (          <SeatOption key={seats} control={control} seats={seats} />        ))}      </FormGroup>      {message ? <FormHelperText>{message}</FormHelperText> : null}    </FormControl>  );}export default function Page() {  const { control, handleSubmit } = useForm<FormValues>({    defaultValues: { seats: null },  });  return (    <form      noValidate      onSubmit={handleSubmit((values) =>        alert(JSON.stringify(values, null, 2)),      )}    >      <Stack direction="column" spacing={2}>        <SeatsField control={control} />        <div>          <MuiButtonElement control={control} type="submit" variant="contained">            Book          </MuiButtonElement>        </div>      </Stack>    </form>  );}

How it works

Checked state. The returned checked is true when the field value equals this radio's value. Values are compared as strings, so a field holding the number 2 checks a radio with value="2". Pass a checked function to decide yourself; it receives the field value.

Stored value. When a radio is selected, the hook stores event.target.value. The DOM always gives a string, so value={2} stores "2". Pass transform.output to convert it, as the demo does with Number(event.target.value). MUI only fires onChange on the radio being selected, so selecting another option is what changes the value.

Empty value. An undefined field value is turned into "" before comparison, so no radio is checked. required fails for undefined, null and "".

Inside a FormControl. If you don't pass disabled or required, the hook takes them from the surrounding MUI FormControl. The demo sets required once on the FormControl and gives each option only the message.

Errors. A radio can't show a message. Read the error at the group level and render it in a FormHelperText. The demo uses useFormState; you can also set composeHelperText: true on one of the options and render its helperText.

What's returned. The usual field props (name, value, onChange, onBlur, ref, disabled, required) plus checked, error, helperText, className and style. title and type are not returned, so set title on the Radio directly if you need it.

Radio has no error or helperText props. Remove them before spreading the result. Remove required as well when the radio sits in a FormControlLabel: otherwise the label shows an asterisk next to every option.

Props

Prop

Type

Return value

Prop

Type

On this page