Multiple Checkbox select one at a time using React Hook Form - reactjs

Hi guys I'm using react hook form for multiple checkbox, Currently all checkbox can be selected.
Any idea how can I select checkbox one at a time?
const formMethods = useForm<BookingSourcesForm>({
defaultValues: { bookingSources: [] },
});
const { fields, append } = useFieldArray<BookingSourcesForm>({
control: formMethods.control,
name: 'bookingSources',
});
{fields.map((field, index) => {
return (
<HStack align="center" justify="space-between" w="100%">
<Controller
name={`bookingSources.${index}.IsDefault` as const}
control={control}
render={({ field }) => (
<Checkbox
isChecked={field.value}
onChange={(e) => {
field.onChange(e.currentTarget.checked);
}}
/>
)}
/>
)
}
}

If only one box should be able to picked you should use a RadioGroup instead.
See: https://chakra-ui.com/docs/components/radio/usage

onChange={(value) => {
getValues(`bookingSources`)?.forEach((booking, idx) => {
setValue(`bookingSources.${idx}.IsDefault` as `bookingSources.0.IsDefault`, false);
});
field.onChange(value);
}}

Related

react hook form material ui checkbox array

i can't get values of checkbox array
const schema = yup.object().shape({
modules: yup.array(),
});
component
{role === "user" &&
divisions.map((division, i) => (
<Box key={division.name}>
<Typography variant='h6'>{division.name}</Typography>
{division.modules.map((m, j) => (
<Controller
key={m.name}
name={`modules[${i}][${j}]`}
control={control}
defaultValue={[division.name, m.name, false]}
render={({ field }) => (
<FormControlLabel
{...field}
label={m.name}
control={
<Checkbox
onChange={(e) => (e.target.value = "chen")}
color='primary'
/>
}
/>
)}
/>
))}
</Box>
))}
when i submit the form without checking anything i got a result like this
{//userinfo, modules: [
//array per division and a nested array for modules access
[ ["Admin-tools", "admin",false ], ["Admin-tools", "Backup",false ] ]
...other divisions and modules
] }
this is the result i expect when i check fields and submit the form
{//userinfo, modules: [
//array per division and a nested array for modules access
[ ["Admin-tools", "admin",true], ["Admin-tools", "Backup",true
] ]
...other divisions and modules
] }
but i got
{//userinfo, modules: [
//array per division and a nested array for modules access
[ [true], [true] ]
...other divisions and modules
] }
You need to pass checked and onChange to your checkbox and append/remove from the form array.
Steps:
Create FromCheckboxes that'll serve as a container to a checkbox array
Loop through your divisions and render a FromCheckbox for each division
Form.js
<form onSubmit={handleSubmit(onSubmit)}>
{divisions.map((division) => (
<Box key={division.name}>
<Typography variant="h6">{division.name}</Typography>
<FormCheckboxes
name="modules"
control={control}
parent={division.name}
options={division.modules}
/>
</Box>
))}
<Button type="submit">Submit</Button>
</form>
FormCheckboxes.js
import { useController } from "react-hook-form";
import { Checkbox, FormControlLabel } from "#material-ui/core";
const FormCheckBoxes = ({ options, ...rest }) => {
const { field } = useController(rest);
const { value, onChange } = field;
return options.map((option) => {
return (
<FormControlLabel
key={option.name}
value={option.name}
label={option.name}
control={
<Checkbox
checked={value.some((formOption) => formOption[1] === option.name)}
onChange={(e) => {
const valueCopy = [...value];
if (e.target.checked) {
valueCopy.push([rest.parent, option.name, true]); // append to array
} else {
const idx = valueCopy.findIndex(
(formOption) => formOption[1] === option.name
);
valueCopy.splice(idx, 1); // remove from array
}
onChange(valueCopy); // update form field with new array
}}
/>
}
/>
);
});
};
export default FormCheckBoxes;
Updated Sandbox
Note that you'll still need to figure out how to tie them all in one form field. Now it won't work with multi-divisions.
if you want the values of the checkbox in order to go to the form values and get it.
you have to give each check box a name property.
ex:
<FormControlLabel
{...field}
label={m.name}
control={
<Checkbox
onChange={(e) => (e.target.value = "chen")}
color='primary'
name='name-one'
/>
}
/>

Using Material UI's Autocomplete using Formik to display different value in dropdown but set different value in formik state

I am trying to use Material UI's Autocomplete with Formik. Here is a custom Autocomplete component I wrote to use with Formik.
import React from "react";
import Autocomplete from "#material-ui/lab/Autocomplete";
import TextField from "#material-ui/core/TextField";
import { fieldToTextField } from "formik-material-ui";
const FormikAutocomplete = ({ textFieldProps, ...props }) => {
const {
form: { setTouched, setFieldValue },
} = props;
const { error, helperText, ...field } = fieldToTextField(props);
const { name } = field;
return (
<Autocomplete
{...props}
{...field}
onChange={(_, value) =>
setFieldValue(name, value)
}
onBlur={() => setTouched({ [name]: true })}
renderInput={(props) => (
<TextField
{...props}
{...textFieldProps}
helperText={helperText}
error={error}
/>
)}
/>
);
};
export default FormikAutocomplete;
Here is how components callled
<Field
name="title"
component={FormikAutocomplete}
options={gender}
getOptionLabel={(option) => option.title_name_long}
textFieldProps={{
label: "Title",
required: true,
variant: "outlined",
margin: "dense",
}}
/>
Now what I intend to do is: If I have a object like
gender=[{gender_name_short:"F",gender_name_long:"Female},{gender_name_short:"M",gender_name_long:"Male}]
I want the Autocomplete dropdown to show options male,female.
But I want the formik state to save M,F respectively once selected from dropdown. Currently the entire object gets saved.
How can this be done?
In FormikAutocomplete component,
use setFieldValue in the onChange of autocomplete
use gender_name_long in getOptionLabel to display Male , Female
use gender_name_short in getOptionSelected to use M or F
With that, finally, when you submit, you will get to see M/F not Male/Female
Working demo
const gender = [
{ gender_name_short: "F", gender_name_long: "Female" },
{ gender_name_short: "M", gender_name_long: "Male" }
];
const validationSchema = object().shape({
// genderObj: array().required("At least one gender is required")
});
const initialValues = {
username: "abc",
country: "",
gender: "M",
birthdate: null
};
const FormikAutocomplete = ({ textFieldProps, ...props }) => {
const {
form: { setTouched, setFieldValue }
} = props;
const { error, helperText, ...field } = fieldToTextField(props);
const { name } = field;
return (
<Autocomplete
{...field}
{...props}
onChange={(_, data) => {
setFieldValue("gender", data.gender_name_short);
}}
onBlur={() => setTouched({ [name]: true })}
// getOptionLabel={item => item.gender_name_long} //<----see here
getOptionLabel={item => {
// console.log( '====>' , typeof item === "string" ? props.options.find(i => i.gender_name_short === item).gender_name_long : item.gender_name_long)
return typeof item === "string"
? props.options.find(i => i.gender_name_short === item)
.gender_name_long
: item.gender_name_long;
}}
// getOptionLabel={item => typeof item === 'string' ? props.options.find(i => i.gender_name_short === item).gender_name_long : item.gender_name_long}
getOptionSelected={(item, current) => {
return item.gender_name_short === current;
}}
// defaultValue={'hi'}
renderInput={props => (
<TextField
{...props}
{...textFieldProps}
helperText={helperText}
error={error}
/>
)}
/>
);
};
const SimpleFormExample = () => (
<div>
<h1>Simple Form Example</h1>
<Formik
initialValues={initialValues}
validationSchema={validationSchema}
validateOnBlur={false}
validateOnChange
onSubmit={(values, { resetForm, setSubmitting }) => {
console.log(values);
resetForm();
// setSubmitting(false);
}}
>
{formik => (
<Form>
<Field
name="gender"
component={FormikAutocomplete}
label="gender"
options={gender}
textFieldProps={{
fullWidth: true,
margin: "normal",
variant: "outlined"
}}
// multiple
/>
<button type="submit">Submit</button>
</Form>
)}
</Formik>
</div>
);
export default SimpleFormExample;

React Hook Forms + Material UI Checkboxes

I am having an error when submiting a form build using React Hook Form and material-ui checkboxes components. The number of checkboxes are build from a list from my api:
<Grid item xs={12}>
<FormControl
required
error={errors.project?.stack ? true : false}
component='fieldset'>
<FormLabel component='legend'>Tech Stack</FormLabel>
<FormGroup>
<Grid container spacing={1}>
{techs.map((option, i) => (
<Grid item xs={4} key={i}>
<FormControlLabel
control={
<Checkbox
id={`stack${i}`}
name='project.stack'
value={option.id}
inputRef={register({required: 'Select project Tech Stack'})}
/>
}
label={option.name}
/>
</Grid>
))}
</Grid>
</FormGroup>
<FormHelperText>{errors.project?.stack}</FormHelperText>
</FormControl>
</Grid>
When the form is submited I got the following error ( several times , 1 for each checkbox rendered ) :
Uncaught (in promise) Error: Objects are not valid as a React child
(found: object with keys {type, message, ref}). If you meant to render
a collection of children, use an array instead.
I don't understand this error. The message apparently says it is a rendering issue, but the component renders fine. The problems happens on submit. Any advices ?
Thank you
UPDATE: if you are using RHF >= 7, you should use props.field to call props.field.value and props.field.onChange.
You can use the default Checkbox Controller:
<FormControlLabel
control={
<Controller
name={name}
control={control}
render={({ field: props }) => (
<Checkbox
{...props}
checked={props.value}
onChange={(e) => props.onChange(e.target.checked)}
/>
)}
/>
}
label={label}
/>
I used the controller example from RHF but had to add checked={props.value}:
https://github.com/react-hook-form/react-hook-form/blob/master/app/src/controller.tsx
I managed to make it work without using Controller.
The props should be inside the FormControlLabel and not inside Checkbox
<Grid item xs={4} key={i}>
<FormControlLabel
value={option.id}
control={<Checkbox />}
label={option.name}
name={`techStack[${option.id}]`}
inputRef={register}
/>
</Grid>
))}
If someone struggle to achieve multiselect checkbox with React material-ui and react-hook-form you can check my codesandbox example
Also, there is a code example provided by react-hook-form in their documentation under useController chapter (don't forget to switch to the checkboxes tab).
Any of that examples works, I´m using this one:
const checboxArray = [{
name: '1h',
label: '1 hora'
},
{
name: '12h',
label: '12 horas'
},
{
name: '24h',
label: '24 horas'
},
{
name: '3d',
label: '3 dias'
},
];
//This inside render function:
{
checboxArray.map((checboxItem) => (
<Controller name = {
checboxItem.name
}
control = {
control
}
key = {
checboxItem.name
}
rules = {
{
required: true
}
}
render = {
({
field: {
onChange,
value
}
}) =>
<
FormControlLabel
control = { <Checkbox
checked = {!!value
}
onChange = {
(event, item) => {
onChange(item);
}
}
name = {
checboxItem.name
}
color = "primary" /
>
}
label = {
checboxItem.label
}
/>
}
/>
))
}
Material UI + React Hook Form + Yup .
Example page:
https://moiseshp.github.io/landing-forms/
Without extra dependences
Show and hide error messages
// import { yupResolver } from '#hookform/resolvers/yup'
// import * as yup from 'yup'
const allTopics = [
'React JS',
'Vue JS',
'Angular'
]
const defaultValues = {
topics: []
}
const validationScheme = yup.object({
topics: yup.array().min(1),
})
const MyForm = () => {
const resolver = yupResolver(validationScheme)
const {
control,
formState: { errors },
handleSubmit
} = useForm({
mode: 'onChange',
defaultValues,
resolver
})
const customSubmit = (data) => alert(JSON.stringify(data))
return (
<form onSubmit={handleSubmit(customSubmit)}>
<FormControl component="fieldset" error={!!errors?.topics}>
<FormLabel component="legend">Topics</FormLabel>
<FormGroup row>
<Controller
name="topics"
control={control}
render={({ field }) => (
allTopics.map(item => (
<FormControlLabel
{...field}
key={item}
label={item}
control={(
<Checkbox
onChange={() => {
if (!field.value.includes(item)) {
field.onChange([...field.value, item])
return
}
const newTopics = field.value.filter(topic => topic !== item)
field.onChange(newTopics)
}}
/>
)}
/>
))
)}
/>
</FormGroup>
<FormHelperText>{errors?.topics?.message}</FormHelperText>
</FormControl>
</form>
)
}
export default MyForm
Here is the simplest way to do it using Controller
<Box>
<Controller
control={control}
name={`${dimension.id}-${dimension.name}`}
defaultValue={false}
render={({ field: { onChange, value } }) => (
<FormControlLabel
control={
<Checkbox checked={value} onChange={onChange} />
}
/>
)}
/>
</Box>

How to use react-hook-form with props and material UI

I'm using Material UI Select as a reusable component, and I want to validate it with react-hook-form by passing props from parent to child component. So far, I've tried to use from RHF, and to pass some props to the child, but somehow error wont disappear when I select option. This is my code
import React from 'react';
import styled from '#emotion/styled';
import { Select, MenuItem } from '#material-ui/core';
import { ASSelect } from '../../ASSelect';
import { Controller, useForm } from 'react-hook-form';
const { register, handleSubmit, watch, errors, control, setValue } = useForm();
const onSubmit = (data: any) => {
console.log(errors);
};
const defaultDashboard = [
{ value: 'chocolate', label: 'Chocolate' },
{ value: 'strawberry', label: 'Strawberry' },
{ value: 'vanilla', label: 'Vanilla' },
];
const Parent = () => {
return (
<Controller
as={
<ASSelect
menuItems={defaultDashboard}
label="Default dashboard*"
handleChange={dashboardHandler}
value={dashboardValue}
}
name="Select"
control={control}
rules={{ required: true }}
onChange={([selected]) => {
return { value: selected };
}}
/>
{errors.Select ? <span>Default dashboard is required</span> : null}
)
}
export {Parent};
const ASSelect = ({menuItems, label, handleChange, value}) => {
return (
<div>
<Select>
{menuItems.map((el, index)=> {
return <MenuItem key={index}>{el.label}</MenuItem>
}}
</Select>
</div>
)}
export {ASSelect};
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
So when I submit a form without selecting anything, the error will popup. But when I select an option, the error will remain there. What am I doing wrong?
You can use Select with Controller from react-hook-forms in the following way:
const SelectController = ({ name, menuItems, control }) => (
<Controller
as={
<Select>
{menuItems.map(({ value, label }, index) => (
<MenuItem key={index} value={value}>
{label}
</MenuItem>
))}
}
</Select>
}
name={name}
control={control}
defaultValue=""
rules={{ required: true }}
/>
);
or use Select with setValue from react-hook-forms as follows:
const SelectSetValue = ({ name, menuItems, setValue }) => {
return (
<Select
name={name}
defaultValue=""
onChange={e => setValue(name, e.target.value)}
>
{menuItems.map(({ value, label }, index) => (
<MenuItem key={index} value={value}>
{label}
</MenuItem>
))}
}
</Select>
);
};
In both cases you get error-validation initially onSubmit event. Error display is updated afterwards for SelectController when you select a value and for SelectSetValue when you select a value and re-trigger onSubmit.
You can check a working example at following the link:
https://codesandbox.io/s/somaterialselecthookform-kdext?file=/src/App.js

Downshift autocomplete onBlur resetting value with Formik

I have a form with a field that needs to show suggestions via an api call. The user should be allowed to select one of those options or not and that value that they type in gets used to submit with the form, but this field is required. I am using Formik to handle the form, Yup for form validation to check if this field is required, downshift for the autocomplete, and Material-UI for the field.
The issue comes in when a user decides not to use one of the suggested options and the onBlur triggers. The onBlur always resets the field and I believe this is Downshift causing this behavior but the solutions to this problem suggest controlling the state of Downshift and when I try that it doesn't work well with Formik and Yup and there are some issues that I can't really understand since these components control the inputValue of this field.
Heres what I have so far:
const AddBuildingForm = props => {
const [suggestions, setSuggestions] = useState([]);
const { values,
errors,
touched,
handleChange,
handleBlur,
handleSubmit,
modalLoading,
setFieldValue,
setFieldTouched,
classes } = props;
const loadOptions = (inputValue) => {
if(inputValue && inputValue.length >= 3 && inputValue.trim() !== "")
{
console.log('send api request', inputValue)
LocationsAPI.autoComplete(inputValue).then(response => {
let options = response.data.map(erlTO => {
return {
label: erlTO.address.normalizedAddress,
value: erlTO.address.normalizedAddress
}
});
setSuggestions(options);
});
}
setFieldValue('address', inputValue); // update formik address value
}
const handleSelectChange = (selectedItem) => {
setFieldValue('address', selectedItem.value); // update formik address value
}
const handleOnBlur = (e) => {
e.preventDefault();
setFieldValue('address', e.target.value);
setFieldTouched('address', true, true);
}
const handleStateChange = changes => {
if (changes.hasOwnProperty('selectedItem')) {
setFieldValue('address', changes.selectedItem)
} else if (changes.hasOwnProperty('inputValue')) {
setFieldValue('address', changes.inputValue);
}
}
return (
<form onSubmit={handleSubmit} autoComplete="off">
{modalLoading && <LinearProgress/>}
<TextField
id="name"
label="*Name"
margin="normal"
name="name"
type="name"
onChange={handleChange}
value={values.name}
onBlur={handleBlur}
disabled={modalLoading}
fullWidth={true}
error={touched.name && Boolean(errors.name)}
helperText={touched.name ? errors.name : ""}/>
<br/>
<Downshift id="address-autocomplete"
onInputValueChange={loadOptions}
onChange={handleSelectChange}
itemToString={item => item ? item.value : '' }
onStateChange={handleStateChange}
>
{({
getInputProps,
getItemProps,
getMenuProps,
highlightedIndex,
inputValue,
isOpen,
}) => (
<div>
<TextField
id="address"
label="*Address"
name="address"
type="address"
className={classes.autoCompleteOptions}
{...getInputProps( {onBlur: handleOnBlur})}
disabled={modalLoading}
error={touched.address && Boolean(errors.address)}
helperText={touched.address ? errors.address : ""}/>
<div {...getMenuProps()}>
{isOpen ? (
<Paper className={classes.paper} square>
{suggestions.map((suggestion, index) =>
<MenuItem {...getItemProps({item:suggestion, index, key:suggestion.label})} component="div" >
{suggestion.value}
</MenuItem>
)}
</Paper>
) : null}
</div>
</div>
)}
</Downshift>
<Grid container direction="column" justify="center" alignItems="center">
<Button id="saveBtn"
type="submit"
disabled={modalLoading}
className = {classes.btn}
color="primary"
variant="contained">Save</Button>
</Grid>
</form>
);
}
const AddBuildingModal = props => {
const { modalLoading, classes, autoComplete, autoCompleteOptions } = props;
return(
<Formik
initialValues={{
name: '',
address: '',
}}
validationSchema={validationSchema}
onSubmit = {
(values) => {
values.parentId = props.companyId;
props.submitAddBuildingForm(values);
}
}
render={formikProps => <AddBuildingForm
autoCompleteOptions={autoCompleteOptions}
autoComplete={autoComplete}
classes={classes}
modalLoading={modalLoading}
{...formikProps} />}
/>
);
}
Got it to work. Needed to use handleOuterClick and set the Downshift state with the Formik value:
const handleOuterClick = (state) => {
// Set downshift state to the updated formik value from handleOnBlur
state.setState({
inputValue: values.address
})
}
Now the value stays in the input field whenever I click out.

Resources