I spent 2 days to look for a code that has a "Check All" checkbox that would select all the checkboxes that are created dynamically. I wanted MUI material components, useForm and Zod.
This is my working solution that might help others:
This is the Zod file:
==== ScheduleDto.ts file ====
import { z } from 'zod';
// This is for the object that will be converted into multiple checkboxes
export const MoreCheckboxesDtoScheme= z.object({
id: z.string(),
name: z.string(),
selected: z.boolean(),
});
// This is for the form with multiple checkboxes and other fields
export const FormDtoScheme = z.object({
firstName: z.string().max(10).optional(),
lastName: z.string().max(10).optional(),
checkAll: z.boolean(),
moreCheckboxes: z.array(MoreCheckboxesDtoScheme),
});
export type FormDto = z.infer<typeof FormDtoScheme >;
export const defaultFormValues = {
firstName: '',
lastName: '',
checkAll: false,
moreCheckboxes: [],
};
This is the React form file:
==== Form.tsx file ====
import React, { useCallback, useEffect, useRef, useState } from 'react';
import {
Accordion,
AccordionDetails,
AccordionSummary,
Button,
Checkbox,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
FormControl,
FormControlLabel,
FormGroup,
InputLabel,
MenuItem,
Select,
Stack,
Typography,
} from '@mui/material';
import CloseIcon from '@mui/icons-material/CloseOutlined';
import { styled } from '@mui/material/styles';
import IconButton from '@mui/material/IconButton';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import { Controller, useFieldArray, useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import {
FormDto,
FormDtoScheme,
defaultFormValues,
} from '@/features/manage-schedules/dto/ScheduleDto.ts';
export interface ScheduleDialogProps {
open: boolean;
onCancel: () => void;
onSubmit: () => void;
}
export interface CheckboxesTemplate {
id: number;
name: string;
selected: boolean;
beginDate: string;
endDate: string;
}
const moreCheckboxesArray: CheckboxesTemplate [] = [];
moreCheckboxesArray.push({
id: 114664,
name: '03/15/2023 SLT by Task',
selected: false,
beginDate: '2020-01-15',
endDate: '2999-12-31',
} as CheckboxesTemplate);
moreCheckboxesArray.push({
id: 105773,
name: 'After HOO',
selected: true,
beginDate: '2022-01-01',
endDate: '2999-12-31',
} as CheckboxesTemplate);
moreCheckboxesArray.push({
id: 104789,
name: 'By Checks (by Hours)',
selected: false,
beginDate: '2022-01-01',
endDate: '2999-12-31',
} as CheckboxesTemplate);
export const ScheduleDialog: React.FC<
ScheduleDialogProps
> = ({ open, onCancel, onSubmit }) => {
const {
control,
getValues,
formState: { isDirty, errors, isValid },
handleSubmit,
setValue,
} = useForm<FormDto>({
resolver: zodResolver(FormDtoScheme),
mode: 'onChange',
defaultValues: { ...defaultFormValues },
});
const { fields, append, remove } = useFieldArray({
name: 'moreCheckboxes',
control,
});
useEffect(() => {
remove();
moreCheckboxesArray.forEach((template: any) => {
append({
id: template.id,
name: template.name,
selected: template.selected,
});
});
}, []);
const handleSubmitAll = () => {
console.log(isDirty);
console.log(errors);
console.log(isValid);
console.log(getValues());
console.log(getValues().moreCheckboxes);
handleSubmit(onSubmit);
};
const handleCheckAll = (event: React.ChangeEvent<HTMLInputElement>) => {
const { checked } = event.target;
getValues().moreCheckboxes.forEach(
(template) => (template.selected = checked)
);
setValue('moreCheckboxes', [...getValues().moreCheckboxes]);
};
//...
return (
//...
<Stack direction="column">
<Accordion>
<AccordionSummary
expandIcon={<ExpandMoreIcon />}
aria-controls="panel1-content"
id="panel1-header">
<AccordionLabel>Many Checkboxes</AccordionLabel>
</AccordionSummary>
<AccordionDetails>
<Stack direction="row">
<FormGroup>
<FormControl>
<Controller
control={control}
name="checkAll"
render={({ field }) => {
return (
<FormControlLabel
{...field}
control={
<Checkbox
{...field}
onChange={(e) => {
field.onChange(e);
handleCheckAll(e);
}}
/>
}
label={<BoldLabel>Check All</BoldLabel>}
/>
);
}}
></Controller>
</FormControl>
{fields.map((item, index) => (
<FormControl key={item.id}>
<Controller
control={control}
name={`moreCheckboxes.${index}.selected`}
render={({ field }) => {
return (
<FormControlLabel
control={<Checkbox {...field} checked={field.value} />}
label={item.name}
/>
);
}}
></Controller>
</FormControl>
))}
</FormGroup>
</Stack>
</AccordionDetails>
</Accordion>
//...
);
};
const SubmitButton = styled(Button)({
paddingTop: '0.5rem',
paddingBottom: '0.5rem',
fontSize: '0.875rem',
weight: 500,
lineHeight: '24px',
letterSpacing: '0.4px',
paddingLeft: '1rem',
paddingRight: '1rem',
});
const CancelButton = styled(SubmitButton)({
marginLeft: '24px',
color: grey[900],
});
const CloseIconButton = styled(IconButton)(({ theme }) => {
return {
position: 'absolute',
right: theme.spacing(2),
top: theme.spacing(1),
color: theme.palette.grey[700],
};
});
const AccordionLabel = styled(Typography)({
paddingTop: '4px',
fontSize: '1rem',
letterSpacing: '0.4px',
fontWeight: 500,
lineHeight: '16px',
color: grey[900],
textAlign: 'left',
});
const BoldLabel = styled(Typography)({
fontWeight: 600,
});