Creating a Group of MUI Checkboxes Controlled by react-hook-form
Tech
Checkbox group
React-hook-form prefers uncontrolled components, which clashes with many component libraries (such as MUI or AntDesign). Thankfully it provides a convenient wrapper in the form of <Controller /> component. That works smoothly in most cases, however I've ran into problems with a group of multiple checkboxes. My wish was to have just one field, that would be represented by an array of checked values, as that is the format used by react-hook-form.
So in the end form data with checked boxes with values "answer-1" and "answer-3" should look like this:
1{"usersAnswers": ["answer-1", "answer-3"]}
A lot of the solutions I've found online didn't work. I suspect they were written for MUI v4 and/or react-hook-form v6... Not completely sure which package was the culprit but in the end the simplest solution seems to be our own state handling of checked boxes. Something like this:
1import { Checkbox, FormControl, FormControlLabel, FormGroup, Typography } from "@mui/material";
2import { useController, useWatch } from "react-hook-form";
3
4export const CheckboxGroup = ({
5 control,
6 name,
7 checkboxes,
8}: { control: any, name: string, checkboxes: { value: string, label: string }[] }) => {
9 const {
10 field: { ref, value, onChange, ...inputProps },
11 formState: { errors },
12 } = useController({
13 name,
14 control,
15 defaultValue: [],
16 })
17 const checkboxIds = useWatch({ control, name: name }) || []
18
19 function handleChange(value: any) {
20 const newArray = [...checkboxIds]
21 const item = value
22
23 if (newArray.length > 0) {
24 const index = newArray.findIndex((x) => x === item)
25 if (index === -1) {
26 newArray.push(item)
27 } else {
28 newArray.splice(index, 1)
29 }
30 } else {
31 newArray.push(item)
32 }
33 onChange(newArray)
34 }
35
36 return (
37 <div>
38 <FormControl>
39 <FormGroup>
40 {checkboxes.map((option: any) => (
41 <FormControlLabel
42 control={
43 <Checkbox
44 checked={value?.some(
45 (checked: any) => checked === option.value
46 )}
47 {...inputProps}
48 inputRef={ref}
49 onChange={() => handleChange(option.value)}
50 />
51 }
52 label={<Typography component="span">{option.label}</Typography>}
53 key={option.value}
54 />
55 ))}
56 </FormGroup>
57 </FormControl>
58 </div>
59 )
60}
61
Usage is simple. Pass the form control
from react-hook-form (or access it directly in the CheckboxGroup
using the useFormContext
hook.)
1// pass the control to our CheckboxGroup from useForm hook from react-hook-form
2export default function PageWithForm() {
3 const { control } = useForm()
4 const checkboxes = [
5 { value: "answer-1", label: "First answer", },
6 { value: "answer-2", label: "Second answer", },
7 { value: "answer-3", label: "Third answer", },
8 ]
9
10 return <CheckboxGroup
11 control={control}
12 checkboxes={checkboxes}
13 name="usersAnswers"
14 />
15}
16