DEV Community

Rex
Rex

Posted on • Edited on

4 2

Use material-ui/pickers with React Hook Form.

React Hook Form(RHF) has a Controller wrapper to help work with external controlled components like material-ui components. So far, I managed to use Controller wrapper element or useController hook to develop my forms without issues until material-ui/pickers. When I wrap KeyboardDatePicker, all functions work with one annoying warning:

Warning: A component is changing an uncontrolled input to be controlled

Inspired by the comment here, I figured out how to remove the above warning.

The idea is to manage a separate state for the date picker and manually wire it up RHF with useEffect.

  1. Setup a state and wire it up to value and onChange props.
    const [date, setDate] = useState<MaterialUiPickersDate>(null);
    return (
    <KeyboardDatePicker
    value={date}
    onChange={(date) => {
    setDate(date);
    }}
    {...otherProps}
    />
    );
  2. Call RHF's setValue to onChange prop so that RHF knows about the picker's value changes. It is important to use the 3rd argument to enable validation and isDirty state update. fieldName is a string representation of the property name the date picker is to hook up in our RHF schema.
    const [date, setDate] = useState<MaterialUiPickersDate>(null);
    const { setValue } = useForm();
    return (
    <KeyboardDatePicker
    value={date}
    onChange={(date) => {
    setDate(date);
    setValue('fieldName', date, { shouldValidate: true, shouldDirty: true});
    }
    {...otherProps}
    />
    );
  3. Hook up the date picker with RHF by registering the field with useEffect. By now, the date picker would update RHF's model, and we can edit/save the date. However, when data arrives from the remote server and RHF form values are updated in our data initialisation process, the date picker is not updated. We deal with it next step.
    const [date, setDate] = useState<MaterialUiPickersDate>(null);
    const {setValue, register } = useForm();
    useEffect(() => {
    register('fieldName');
    }, [register]);
    return (
    <KeyboardDatePicker
    value={date}
    onChange={(date) => {
    setDate(date);
    setValue('fieldName', date, { shouldValidate: true, shouldDirty: true});
    }
    {...otherProps}
    />
    );
  4. To update the date picker when data initialisation happens, we need to watch the value changes coming from the RHF model and update the date picker. RHF's watch is the perfect tool for this job. Note, the value coming from the remote could be undefined. We default it to null here.
    const [date, setDate] = useState<MaterialUiPickersDate>(null);
    const { register, getValues, setValue } = useForm();
    const value = getValues('fieldName') as Date;
    useEffect(() => {
    register('fieldName');
    }, [register]);
    useEffect(() => {
    setDate(value || null);
    }, [setDate, value]);
    return (
    <KeyboardDatePicker
    value={date}
    onChange={(date) => {
    setDate(date);
    setValue('fieldName', date, { shouldValidate: true, shouldDirty: true });
    }}
    {...otherProps}
    />
    );
    You may have noticed that in the above code, we have setDate called in two places. Since the useEffect will watch the value changes and update the state with setDate, we can safely delete setDate in our onChange prop.

Final code:

const [date, setDate] = useState<MaterialUiPickersDate>(null);
const { register, getValues, setValue } = useForm();
const value = getValues('fieldName') as Date;
useEffect(() => {
register('fieldName');
}, [register]);
useEffect(() => {
setDate(value || null);
}, [setDate, value]);
return (
<KeyboardDatePicker
value={date}
onChange={(date) => setValue('fieldName', date, { shouldValidate: true, shouldDirty: true })}
{...otherProps}
/>
);

Happy coding…

Update: 07/03/2021

For validation and general usage of the above date-picker, see below my own use case with Yup:

// define schema
export const companySchema = object({
yearEndDate: date().nullable().typeError('Invalid Date'),
});
export type Company = Asserts<typeof companySchema>;
//deconstruct `control` and `errors`
const { reset, control, handleSubmit, errors } = useForm<Company>({
resolver: yupResolver(companySchema)
});
//assign `error` and `helperText`
<EpicDatePicker
control={control}
name={'yearEndDate'}
label={'Year End Date'}
error={!!errors.yearEndDate}
helperText={errors.yearEndDate?.message}
/>

Image of Docusign

🛠️ Bring your solution into Docusign. Reach over 1.6M customers.

Docusign is now extensible. Overcome challenges with disconnected products and inaccessible data by bringing your solutions into Docusign and publishing to 1.6M customers in the App Center.

Learn more

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs