react-hook-form material-ui (FormControlLabel + Checkbox) using Controller - reactjs

to use Material-ui in react-hook-form you should use <Controller and the method render instead of "as = {xy-control}" Also should not mix controller and inputRef = {register}.
A single control is also no problem.
But there is a compound control in Material-ui. "FormControlLabel + CheckBox" how do you integrate this control in the controller. All my attempts have failed.
This is how it works but I would like the FormControlLaben to be enclosed by the controller.
Does somebody has any idea?
<Controller
name="password"
control={control}
defaultValue={""}
render={(props) => <TextField {...props}
variant="outlined"
margin="normal"
required
fullWidth
label="Password"
type="password"
id="password"
autoComplete="current-password"
/>}
/>
<FormControlLabel
control={
<Checkbox
inputRef={register}
name="remember"
/>
}
label="remember"
/>
{/*That works, but it requires an OnChange. Why can't the controller bind it?*/}
<FormControlLabel
control={
<Controller
name={"remember2"}
control={control}
render={(props) => (
<Checkbox
{...props}
onChange={(e) => props.onChange(e.target.checked)}
/>
)}
/>
}
label="remember"
/>

Here's how I did it
<FormControlLabel
control={
<Controller
control={control}
inputRef={register}
name='controlName'
render={({onChange, value}) => (
<Checkbox
onChange={e => onChange(e.target.checked)}
checked={value}
/>
)}
/>
}
label="This is a label"
/>

From v7, I used this :
<Controller
name='contactAutre'
control={control}
defaultValue={false}
render={({ field }) => (
<FormControlLabel
control={<Checkbox {...field} />}
label='Autre'
/>
)}
/>

this was enough for me w/ Mui 5 & RHF 7
const { watch, register } = useForm<{saveEmail: boolean}>({
defaultValues: { saveEmail: true },
});
...
<FormControlLabel
control={
<Checkbox {...register('saveEmail')} checked={watch('saveEmail')} />
}
label="save email"
/>

Credit to Leo Roese for his example for Textfield. However you can apply the same logic for Checkboxes but slightly tweak.
const { control, formState: { errors } } = useForm()
<Controller
name="privacyAccept"
control={control}
render={({ field }) => (
<>
<FormControlLabel
control={<Checkbox {...field} />}
label="I agree to the privacy policy"
/>
{errors.privacyAccept && (
<FormHelperText error>{errors.privacyAccept.message}</FormHelperText>
)}
</>
)}
/>

Another way to make things more controlled:
<FormControlLabel
label="label here"
control={
<Controller
name="isSeparatedDelivery"
control={control}
render={({ field: { onChange, value } }) => (
<Checkbox
checked={value}
onChange={(e) => onChange(e.target.checked)}
/>
)}
/>
}
/>
or
<Controller
control={control}
name="selected"
render={({ field: { onChange, value } }) => (
<FormControlLabel
control={
<Checkbox
label="Sélectionner la question"
checked={value}
onChange={onChange}
/>
}
label="Sélectionner la question"
/>
)}
/>

Related

Validating radio button with React Hook Form

I have a custom radio button component in React looking like this:
export default function SingleSelectionButton(props) {
const {
values, name, required, errors, defaultData,
xs, md, register, infoBox, infoBoxContent, validateField } = props;
return (
<>
<Form.Group>
<Form.Label>
<FormattedMessage
id={name}
/>
</Form.Label>
{values.map((value) => (
<span key={value} className="SingleSelectionButton">
<input
{...register(name, { required: required}
id={name + value}
type="radio"
name={name}
value={value}
defaultChecked={defaultData.primary[name] === value}
/>
<label htmlFor={name + value}>
<FormattedMessage id={value} />
</label>
</span>
))}
</Form.Group>
</>
);
};
I call it like this, using an array for the different values:
<SingleSelectionButton
name="us_person"
md={6}
values={["yes", "no"]}
register={register}
required={true}
errors={errors}
validateField="no"
/>
The validation with required is working fine.
The problem is that I don't manage to validate a value in particular.
I have tried the following:
<input
{...register(name, { required: required, validate: value => value === validateField })}
id={name + value}
type="radio"
name={name}
value={value}
defaultChecked={defaultData.primary[name] === value}
/>
And also:
<input
{...register(name, { required: required, pattern: validateField })}
id={name + value}
type="radio"
name={name}
value={value}
defaultChecked={defaultData.primary[name] === value}
/>
So far no joy. What am I missing?

Password field showing "password is required" even after a value is passed

I am working on a login form with Next Js as frontend and uses MUI 5. The issue is that even after i pass a value to password field, when i click on login button it is showing password field is required. Also when i try to console log the username and password to test it, only username got displayed in the console. please help me to understand what mistake I am doing. Please find the login form below
Please find the code MUI code for both username and password(I am making use of a purchased theme)
<form noValidate autoComplete='off' onSubmit={handleSubmit}>
<FormControl fullWidth sx={{ mb: 4 }}>
<Controller
name='Username'
control={control}
rules={{ required: true }}
render={({ field: { value, onChange, onBlur } }) => (
<TextField
autoFocus
label='Username'
value={value}
onBlur={onBlur}
//onChange={onChange}
onChange={(e)=> setUsername(e.target.value)}
error={Boolean(errors.user)}
// placeholder='admin#materio.com'
/>
)}
/>
{errors.user && <FormHelperText sx={{ color: 'error.main' }}>{errors.user.message}</FormHelperText>}
</FormControl>
<FormControl fullWidth>
<InputLabel htmlFor='auth-login-v2-password' error={Boolean(errors.password)}>
Password
</InputLabel>
<Controller
name='password'
control={control}
rules={{ required: true }}
render={({ field: { value, onBlur } }) => (
<OutlinedInput
value={value}
onBlur={onBlur}
label='Password'
//onChange={onChange}
onChange={(e)=> setPassword(e.target.value)}
id='auth-login-v2-password'
error={Boolean(errors.password)}
type={showPassword ? 'text' : 'password'}
endAdornment={
<InputAdornment position='end'>
<IconButton
edge='end'
onMouseDown={e => e.preventDefault()}
onClick={() => setShowPassword(!showPassword)}
>
{showPassword ? <EyeOutline /> : <EyeOffOutline />}
</IconButton>
</InputAdornment>
}
/>
)}
/>
{errors.password && (
<FormHelperText sx={{ color: 'error.main' }} id=''>
{errors.password.message}
</FormHelperText>
)}
</FormControl>

RHF not resetting values in an MUI checkbox group

I am trying to reset my form with the reset() method and, it is working partially. All of the form's fields get reset, except for the checkboxes group.
I have tried three different ways of implementing an MUI CheckboxesGroup but, I always get the same result.
You can see a working example in this sandbox
Checkbox one implementation
<FormControl error={error} required={required} component="fieldset">
<FormLabel component="legend">{label}</FormLabel>
<Controller
name={name}
render={({ field: { onBlur: rhfonBlur, onChange: rhfonChange } }) => (
<FormGroup onBlur={rhfonBlur}>
{Object.keys(options).map((key) => {
return (
<FormControlLabel
label={key}
key={key}
control={
<Checkbox onChange={(e) => rhfonChange(handleCheck(key))} />
}
/>
);
})}
</FormGroup>
)}
control={control}
/>
<FormHelperText>{helperText}</FormHelperText>
</FormControl>
Checkbox two implementation
<FormControl error={error}>
<FormLabel component="legend">{label}</FormLabel>
<FormGroup>
{Object.keys(options).map((key) => {
return (
<FormControlLabel
key={key}
control={<Checkbox {...register(name)} />}
value={key}
label={key}
/>
);
})}
</FormGroup>
<FormHelperText>{helperText}</FormHelperText>
</FormControl>
Checkbox three implementation
<FormControl required error={error} component="fieldset" variant="standard">
<FormLabel component="legend">{label}</FormLabel>
<Controller
control={control}
name={name}
render={({ field: { onChange: rfhonChange, value } }) => {
// console.log("value:", value);
return (
<FormGroup>
{Object.keys(state).map((key) => {
return (
<FormControlLabel
label={key}
key={key}
control={
<Checkbox
onChange={(e) => {
rfhonChange(handleChange(e));
}}
checked={state[key]}
name={key}
/>
}
/>
);
})}
</FormGroup>
);
}}
/>
<FormHelperText>{helperText}</FormHelperText>
</FormControl>
Your third implementation was the correct way of doing it by using RHF's <Controller /> component. The reason why it wasn't working was because of the following:
you're not updating RHF's internal state for your field as you are only passing a function to the onChange handler. There is no need to use an additional useState here
your initial value is a boolean (false) but you are using an object inside the <CheckboxThree /> component
So basically you can simplify the component to the following (in you're example it isn't clear if the field value should be an object or an array containing the selected options - so the example i made is using an array):
const CheckboxThree = ({
control,
error,
helperText,
label,
name,
options
}) => {
return (
<FormControl required error={error} component="fieldset" variant="standard">
<FormLabel component="legend">{label}</FormLabel>
<Controller
control={control}
name={name}
render={({ field: { onChange, value, ref, ...field } }) => (
<FormGroup>
{Object.keys(options).map((key) => {
return (
<FormControlLabel
label={key}
key={key}
control={
<Checkbox
{...field}
name={key}
checked={value.some((option) => option === key)}
onChange={(event, checked) => {
if (checked) {
onChange([...value, event.target.name]);
} else {
onChange(
value.filter((value) => value !== event.target.name)
);
}
}}
inputRef={ref}
/>
}
/>
);
})}
</FormGroup>
)}
/>
<FormHelperText>{helperText}</FormHelperText>
</FormControl>
);
};

reactjs: Es6 unable to understand the syntax

I am using react hook forms and encountered this example of using Controller for material ui TextField
({ field: { ref, ...field } }) => (
<TextField
{...field}
inputRef={ref}
fullWidth
required
label="Current Password"
type="password"
margin="dense"
/>
)
What does the below mean
({ field: { ref, ...field } })
and also {...field} (what it expands to)
<TextField
{...field}
inputRef={ref}
fullWidth
required
label="Current Password"
type="password"
margin="dense"
/>
The total code is
<Controller
name="old_password"
control={control}
render={({ field: { ref, ...field } }) => (
<TextField
{...field}
inputRef={ref}
fullWidth
required
label="Current Password"
type="password"
margin="dense"
/>
)}
/>
It's jus't a regular component... Think about it like this:
const Component = (props) =>{
const {ref, ...field} = props.field
return(
<TextField
{...field}
inputRef={ref}
fullWidth
required
label="Current Password"
type="password"
margin="dense"
/>
)
}
...field is just the spreaded original field props.field with ref property subtracted. In this case necessary cause TextField expects a ref to be hold by inputRef

react-hook-form and MUI FormControl

So I am trying to register some radio buttons for my form, however it will not register.
<FormControl component="fieldset" inputRef={register({ required: true })}>
<FormLabel component="legend">Promoting</FormLabel>
<RadioGroup aria-label="promoting" name="promoting" value={promoting} onChange={handleChange} inputRef={register({ required: true })}>
<FormControlLabel value="business" control={<Radio />} label="Business" />
<FormControlLabel value="nonprofit" control={<Radio />} label="Non-Profit" />
<FormControlLabel value="event" control={<Radio />} label="Event" />
</RadioGroup>
</FormControl>
I am wondering what I am doing wrong
Full code:
export default function BuildAd() {
const [promoting, setValue] = React.useState('female');
const handleChange = (event) => {
setValue(event.target.value);
};
const { register, handleSubmit, errors } = useForm();
const onSubmit = data => console.log(data);
console.log(errors);
return (
<div style={{ padding: 16, margin: 'auto', maxWidth: 600 }}>
<CssBaseline />
<Typography variant="h5" align="center" component="h2" gutterBottom>
Create your campaign
</Typography>
<Paper style={{ padding: 16 }}>
<form onSubmit={handleSubmit(onSubmit)}>
<input type="text" placeholder="First name" name="First name" ref={register({required: true, maxLength: 80})} />
<input type="text" placeholder="Last name" name="Last name" ref={register({required: true, maxLength: 100})} />
<input type="text" placeholder="Email" name="Email" ref={register({required: true, pattern: /^\S+#\S+$/i})} />
<input type="tel" placeholder="Mobile number" name="Mobile number" ref={register({required: true, minLength: 6, maxLength: 12})} />
<select name="Title" ref={register({ required: true })}>
<option value="Mr">Mr</option>
<option value="Mrs">Mrs</option>
<option value="Miss">Miss</option>
<option value="Dr">Dr</option>
</select>
<input name="Developer" type="radio" value="Yes" ref={register({ required: true })}/>
<input name="Developer" type="radio" value="No" ref={register({ required: true })}/>
<Typography variant="h5">What are you Advertising</Typography>
<Box m={2}/>
<FormControl component="fieldset" inputRef={register({ required: true })}>
<FormLabel component="legend">Promoting</FormLabel>
<RadioGroup aria-label="promoting" name="promoting" value={promoting} onChange={handleChange} inputRef={register({ required: true })}>
<FormControlLabel value="business" control={<Radio inputRef={register({ required: true })} />} label="Business" />
<FormControlLabel value="nonprofit" control={<Radio inputRef={register({ required: true })} />} label="Non-Profit" />
<FormControlLabel value="event" control={<Radio inputRef={register({ required: true })} />} label="Event" />
</RadioGroup>
</FormControl>
<input type="submit" />
</form>
</Paper>
</div>
);
}
You can fallback on controllable components by using Controller if it doesn't work.
In your case, I suspect it's because the name props passed to your RadioGroup is not the same as the name attribute when passed into native inputs like input or select.
react-hook-form V7
const { handleSubmit, control } = useForm({
defaultValues: {
promoting2: '',
},
});
<FormControl component="fieldset">
<FormLabel component="legend">Promoting</FormLabel>
<Controller
rules={{ required: true }}
control={control}
name="promoting2"
render={({ field }) => (
<RadioGroup {...field}>
<FormControlLabel
value="business"
control={<Radio />}
label="Business"
/>
<FormControlLabel
value="nonprofit"
control={<Radio />}
label="Non-Profit"
/>
<FormControlLabel
value="event"
control={<Radio />}
label="Event"
/>
</RadioGroup>
)}
/>
</FormControl>
react-hook-form V6
const { register, handleSubmit, errors, control } = useForm({
promoting: '',
});
const onSubmit = (data) => alert(JSON.stringify(data, null, 2));
<form onSubmit={handleSubmit(onSubmit)}>
<FormControl component="fieldset">
<FormLabel component="legend">Promoting</FormLabel>
<Controller
rules={{ required: true }}
control={control}
name="promoting"
as={
<RadioGroup>
<FormControlLabel
value="business"
control={<Radio />}
label="Business"
/>
<FormControlLabel
value="nonprofit"
control={<Radio />}
label="Non-Profit"
/>
<FormControlLabel
value="event"
control={<Radio />}
label="Event"
/>
</RadioGroup>
}
/>
</FormControl>
<input type="submit" />
</form>
If you want to read the value when onChange fires, you cannot do that when passing the element to the as props:
<Controller
{...}
as={
<MyComponent
onChange={handleChange} // handleChange will never be called
/>
}
/>
The reason is because Controller will copy the element that you pass in and pass its own onChange props that will override yours. See here and here.
To fix that problem, you need to use another way to pass the element down by using render props to gain more control on how you should render your components.
<Controller
rules={{ required: true }}
control={control}
name="promoting2"
render={({ name, onBlur, onChange, value }) => (
<RadioGroup
value={value}
onBlur={onBlur}
onChange={(e) => {
onChange(e);
console.log(e.target.value); // will be called this time
}}
>
<FormControlLabel
value="business"
control={<Radio />}
label="Business"
/>
{...}
</RadioGroup>
)}
/>

Resources