Piplup
@piplup/rhf-adaptersmui-x-date-pickersHooks

useMuiXYearCalendarAdapter

Returns adapter props for MUI X YearCalendar with React Hook Form, including how year selection interacts with day-level date validation.

useMuiXYearCalendarAdapter returns React Hook Form props for MUI X's YearCalendar, a scrollable grid of years. It's what MuiXYearCalendarElement uses internally.

Use the hook instead of the element when you need to show the validation message, label the grid, or reuse a year field with fixed limits, such as a start year that can't be in the future.

Import

import { useMuiXYearCalendarAdapter } from "@piplup/rhf-adapters/mui-x-date-pickers";

Usage

This JoinedYearField lists years from 1970 to the current year, newest first, using minDate and maxDate. Submit without choosing to see the required message replace the hint under the grid.

Year you joined the company

Pick the year you started.

"use client";import dayjs, { type Dayjs } from "dayjs";import FormHelperText from "@mui/material/FormHelperText";import Stack from "@mui/material/Stack";import Typography from "@mui/material/Typography";import { LocalizationProvider, YearCalendar } from "@mui/x-date-pickers";import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";import { MuiButtonElement } from "@piplup/rhf-adapters/mui-material";import { useMuiXYearCalendarAdapter } from "@piplup/rhf-adapters/mui-x-date-pickers";import { type Control, useForm } from "react-hook-form";type FormValues = { joinedIn: Dayjs | null };function JoinedYearField({ control }: { control: Control<FormValues> }) {  // Remove the props YearCalendar does not accept; it would pass them to its root <div>.  const {    error,    helperText,    required: _required,    shouldDisableDate: _shouldDisableDate,    shouldDisableMonth: _shouldDisableMonth,    ...adapter  } = useMuiXYearCalendarAdapter<    Dayjs,    FormValues,    "joinedIn",    HTMLDivElement  >({    control,    name: "joinedIn",    required: true,    composeHelperText: true,    helperText: "Pick the year you started.",    // minDate and maxDate also set the range of years the grid lists.    minDate: dayjs("1970-01-01"),    maxDate: dayjs(),    messages: {      required: "Select the year you joined",      maxDate: "That year hasn't happened yet",      minDate: "We were founded in 1970",    },  });  return (    <div>      <Typography variant="subtitle2" id="joined-in-label">        Year you joined the company      </Typography>      <YearCalendar gridLabelId="joined-in-label" yearsOrder="desc" {...adapter} />      <FormHelperText error={error}>{helperText}</FormHelperText>    </div>  );}export default function Page() {  const { control, handleSubmit } = useForm<FormValues>({    defaultValues: { joinedIn: null },  });  return (    <LocalizationProvider dateAdapter={AdapterDayjs}>      <form        noValidate        onSubmit={handleSubmit((values) => {          const years = dayjs().year() - (values.joinedIn?.year() ?? 0);          alert(`${years} years with the company`);        })}      >        <Stack direction="column" spacing={2}>          <JoinedYearField control={control} />          <div>            <MuiButtonElement control={control} variant="contained" type="submit">              Save            </MuiButtonElement>          </div>        </Stack>      </form>    </LocalizationProvider>  );}

How it works

The wiring is the same as useMuiXDateCalendarAdapter: there is no text field, so render the message yourself. YearCalendar passes unknown props to its root <div>, so remove error, helperText, required, shouldDisableDate and shouldDisableMonth from the result before spreading, as the example does. YearCalendar only understands shouldDisableYear. The hook still validates the other two if you pass them. The returned onBlur lands on the root too, so leaving a year button marks the field as touched.

The value is still a full date. Selecting a year sets the year on the current value and keeps the month and day. If the field is empty, YearCalendar starts from 1 January of the current year, so picking 2015 stores 1 January 2015. Read it with .year() as the example does, or store a number in a separate field if your API expects one.

minDate and maxDate limit the grid. The hook spreads them onto YearCalendar, so they also decide which years are listed. Without them, the grid uses the LocalizationProvider defaults and runs from 1900 to 2099.

Validation compares days, not years. The hook runs the same date validation as useMuiXDatePickerAdapter, with its keys and default messages. An upper limit such as maxDate: dayjs() or disableFuture works for a first pick, because 1 January of the current year is never in the future. Lower limits do not.

With disablePast, picking the current year on an empty field stores 1 January, which is in the past for most of the year, so the form rejects a year the grid shows as enabled. A minDate that is not 1 January has the same problem for its own year. Use shouldDisableYear for year limits instead, for example (year) => year.year() < dayjs().year(). The same applies to upper limits once a value exists: changing 20 December 2015 to the current year stores 20 December of this year, which is still in the future until that day.

Props

Prop

Type

Return value

Prop

Type

On this page