React Final Form and Material UI, disabling element when specific radio button is selected - reactjs

New to React Final Form and creating a form with Material UI. I have a date picker disabled by default and want to enable it when a certain radio is selected. I am unsure how to get the form to enable the picker when the 'schedule' radio button is selected and disable it when not.
My Radio group component:
<RadioGroup defaultValue={defaultVal} name={name}>
{items.map(item => (
<React.Fragment key={item.value}>
<FormControlLabel
name={item.name}
label={item.label}
value={item.value}
control={
<Field
name={name}
type="radio"
render={({ input: { name, label, value, onChange } }) => (
<Radio
name={name}
label={label}
value={value}
onChange={onChange}
/>
)}
/>
}
/>
</React.Fragment>
))}
</RadioGroup>
Form Component:
const items=[
{
name: 'send',
value: 'immediately',
label: 'Immediately
},
{
name: 'send',
value: 'schedule',
label: 'Schedule
}
]
<Form onSubmit={onSubmit}>
{({ handleSubmit }) => (
<form onSubmit={handleSubmit}>
<Schedule items={items} />
...
</form>
)}
</Form>
My Schedule Component:
<RadioGroup name="options" defaultValue="immediately" items={items}/>
<TimePicker disabled={true} />

You need to get radio field state in Schedule component. Check this link from final form docs: https://codesandbox.io/embed/pyrwplknom?codemirror=1

Related

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>
);
};

Why can't I read Dropdown values by Formik in React?

I am using Formik and Yup for a form control in react application. I use also Semantic UI. In the application, when I click the submit button, whereas I can read the values from FormField elements and see them on Console, I can not get the values from Dropdown elements and they appear blank on Console. How can I solve this out?
return (
<div>
<Card fluid>
<Card.Content>
<Card.Header>Create A New Job Ad</Card.Header>
<Divider inverted />
<Formik
initialValues={initialValues}
validationSchema={jobAdCreateSchema}
onSubmit={(values) => {
console.log(values);
}}
>
<Form className="ui form">
<label>Job Title</label>
<FormField>
<Field name="jobTitle" placeholder="Select a Job Title"></Field>
</FormField>
<FormField>
<label>City</label>
<Dropdown
name="city"
placeholder="Select a City"
fluid
selection
options={cityOption}
/>
</FormField>
<FormField>
<Button color="green" type="submit">
Submit
</Button>
</FormField>
</Form>
</Formik>
</Card.Content>
</Card>
</div>
);
The issue is with the way the Dropdown component is rendered currently. You need to make use of the setFieldValue provided by Formik to manually update the Formik state.
<Formik
initialValues={{
city: ""
}}
onSubmit={(values) => console.log(values)}
>
{({ values, setFieldValue }) => (
<div>
<pre>{JSON.stringify(values, undefined, 2)}</pre>
<Dropdown
selection
placeholder="Select a City"
options={[
{ value: "NY", text: "New York" },
{ value: "LA", text: "Los Angeles" },
{ value: "SF", text: "San Fransico" }
]}
value={values.city}
onChange={(value) => setFieldValue("city", value)}
/>
</div>
)}
</Formik>
Working Sandbox

Register third party- custom component with react-hook-form

I am using react-hook-form and using third party DatePicker. Since it's a custom component using it as a controlled component to register it. This works fine
<Controller
control={control}
name="reviewStartDate"
render={({ field: { onChange, onBlur, value } }) => (
<DatePicker
className={`form-control ${errors.reviewStartDate ? 'is-invalid' : ''}`}
customInput={<input />}
wrapperClassName="datePicker"
onChange={onChange}
onBlur={onBlur}
selected={value ? new Date(value) : ''}
dateFormat='dd-MMM-yyyy'
/>
)}
/>
Similarly/however, I am using thirdparty Multiselect. Here the value is not being registered. It does show the selected value but when I submit the form the value is not present in data.
<Controller
control={control}
name="rootCauseAnalysisCategory"
render={({ field: { value } }) => (
<Multiselect
options={rootCauseAnalysisCategorys}
isObject={false}
showCheckbox={true}
hidePlaceholder={true}
closeOnSelect={false}
selectedValues={value}
/>
)}
/>
Similarly
The <MultiSelect /> component has onSelect and onRemove props, so you can just pass onChange to them. This will work because they both have the signature that the first argument is an array containing the current selected values.
<Controller
control={control}
name="rootCauseAnalysisCategory"
defaultValue={[]}
render={({ field: { value, onChange } }) => (
<Multiselect
options={rootCauseAnalysisCategorys}
isObject={false}
showCheckbox={true}
hidePlaceholder={true}
closeOnSelect={false}
onSelect={onChange}
onRemove={onChange}
selectedValues={value}
/>
)}
/>
UPDATE
If you want to access the current value for rootCauseAnalysisCategory, you have to use watch. Please note, that it is also important to either provide a defaultValue at the <Controller /> field level or call useForm with defaultValues. In the example i passed the defaultValue at the field level.
function App() {
const { control, handleSubmit, watch } = useForm();
const onSubmit = (data) => {
console.log(data);
};
const rootCauseAnalysisCategorys = ["Category 1", "Category 2"];
const rootCauseAnalysisCategory = watch("rootCauseAnalysisCategory");
return (
<div className="App">
<form onSubmit={handleSubmit(onSubmit)}>
<Controller
control={control}
name="rootCauseAnalysisCategory"
defaultValue={[]}
render={({ field: { value, onChange } }) => (
<Multiselect
options={rootCauseAnalysisCategorys}
isObject={false}
showCheckbox={true}
hidePlaceholder={true}
closeOnSelect={false}
onSelect={onChange}
onRemove={onChange}
selectedValues={value}
/>
)}
/>
{rootCauseAnalysisCategory?.includes("Category 1") && <p>Category 1</p>}
<input type="submit" />
</form>
</div>
);
}

antd disable field based on checkbox

I'm using dynamic form in ant design library
I create checkbox and What I want to do is disable this timepicker field based on
the checkbox is checked or not
how can I do this
const Demo = () => {
const onFinish = (values) => {console.log(values)};
return (
<Form
>
{/* dynamically created card */}
<Form.List name="except">
{(fields, { add, remove }) => (
{fields.map((field) => (
<Space key={field.key}>
<MinusCircleOutlined
onClick={() =>remove(field.name)}/>
<Form.Item
{...field}
valuePropName={[field.name,'checked']} className="mb-3"
>
<Checkbox>مغلق</Checkbox>
</Form.Item>
{/* ------------- from_hour -------------*/}
<Form.Item
{...field}
name={[field.name,'from_hour']}
fieldKey={[field.fieldKey,'from_hour']}
>
<TimePicker
use12Hours
placeholder="اختر"
format="hh:mm A"
/>
</Form.Item>
</Space>
))}
)}
</Form.List>
</Form>
);
};
You need state. You can control the checked value of the checkbox and use that state to conditionally render another form item.
There is an example of this behavior in the docs.

Using React Hook Form with Material UI Select Component

I've been having trouble finding the correct way to use React Hook Form with certain Material UI components. I can get it to work with simple Text Fields but when it comes to nested components I can't figure it out.
Specifically, I am trying to submit the data from the selection in a Select component with child MenuItems.
See the notes in the code:
export default function NewGoalPane() {
const classes = useStyles();
const {register, handleSubmit} = useForm();
return (
<div className={classes.root}>
<CssBaseline />
<form noValidate onSubmit={handleSubmit((data) => alert(JSON.stringify(data)))}>
<main className={classes.content}>
<div className={classes.text_field_section}>
//This text field works and React Hook Form reads the data correctly.
<TextField
label="Goal Title"
name="goalTitle"
defaultValue=""
inputRef={register}/>
</div>
//This Select component does not read the data from the selection of the MenuItems.
<div className={classes.section}>
<Select
label="Repeating"
name="repeating"
defaultValue={true}
inputRef={register} // Here I call register like all the React Hook Form docs say
>
<MenuItem value={true}>Yes</MenuItem>
<MenuItem value={false}>No</MenuItem>
</Select>
</div>
</main>
</form>
</div>
);
}
How do I fix the Select component so that React Hook Form collects the data in the form submission?
I found Material UI's TextField simple as it requires less code and also you can avoid using controller and Select component. This is my solution.
<TextField
select
name: 'city'
inputRef={register({ required: true })}
onChange={e => setValue('city', e.target.value, {shouldValidate: true})}
label="City"
defaultValue="">
{cityList.map((option, index) => (
<MenuItem key={index} value={option}>
{option}
</MenuItem>
))}
</TextField>
{errors.city && <ErrorText>City is required</ErrorText>}
If you are using v7 the best way was to use controllers for Material Ui components
import { useForm, Controller } from 'react-hook-form';
//component
const methods = useForm();
const { control } = methods;
<Controller
name="city"
control={control}
defaultValue=""
rules={{ required: 'City Required' }}
render={({ field: { onChange, value } }) => (
<TextField
fullWidth
label="City"
variant="outlined"
value={value}
onChange={onChange}
/>
)}
/>

Resources