Passing Formik input values in nested forms using Material UI - reactjs

I'm currently having problems when using Formik with MaterialUI forms. Specifically,
I am having trouble passing Formik input values in nested forms using Material UI and having a small issue where Formik.handleChange is changing the value from number to string.
I have multiple forms that are split with Stepper component in Material UI. Since I am still learning, I tried to wrap Formik on one of the steps (later on I am going to wrap the whole stepper). Here's how it looks:
{activeStep === 0 && (
<Formik
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={(values, { setSubmitting }) => {
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
setSubmitting(false);
}, 400);
}}
>
{formik => (
<form onSubmit={formik.handleSubmit}>
<div className="col-xl-6">
<Portlet fluidHeight>
<PortletHeader title="Incident Type" />
<PortletBody>
<IncidentSelect formik={formik} />
</PortletBody>
</Portlet>
</div>
</form>
)}
</Formik>
)}
The problem is inside the IncidentSelect form, Formik handleChange does not seem to change the selected radioButton. When I inspected with React Developer Tools in Chrome it seems that Formik.handleChange is changing the value from 0 to "0". How do I fix this?
Also, following the tutorial, I'm unsure how I can abstract my components? Note that DateTimePicker is using material-ui/pickers. I'm not sure how I am going to pass the value to Formik.
Any help is appreciated.
Thanks
function IncidentSelect(props) {
const [value, setValue] = React.useState("female");
const handleRadioChange = e => {
console.log(props.formik.getFieldProps("incidentType"));
setValue(e.target.value);
};
return (
<>
<FormControl component="fieldset">
<FormLabel component="legend" required>
Incident Type:
</FormLabel>
<RadioGroup
aria-label="Incident Type"
name="incidentType"
value={value}
onChange={handleRadioChange}
{...props.formik.getFieldProps("incidentType")}
>
<FormControlLabel
value={0}
control={<Radio />}
label="Injury To Guest"
/>
<FormControlLabel
value={1}
control={<Radio />}
label="Injury To Worker"
/>
<FormControlLabel
value={2}
control={<Radio />}
label="Incident / Accident"
/>
<FormControlLabel
value={3}
disabled
control={<Radio />}
label="OSH / Kids Camp"
/>
</RadioGroup>
</FormControl>
<DateTimePicker
label="Signed Date"
variant="outlined"
className={classes.margin}
value={selectedDate}
onChange={handleDateChange}
/>
</>
);
}

As stated in the tutorial, it is easier to abstract the component that you want to use. Giving you chances to reuse them later in your application and more readable code.
Formik provides you with useFields API to get the props of the field via hooks. useFields is looking for the name props of your component to find the corresponding field.
Thus RadioGroup from MaterialUI can be extracted as follows:
export const IncidentRadioButton = ({ options, ...props }) => {
const [field, meta] = useField(props);
return (
<>
<RadioGroup {...field} {...props}>
{options.map((option, index) => (
<FormControlLabel
control={<Radio />}
label={option.name}
value={option.value}
key={index}
/>
))}
</RadioGroup>
{meta.touched && meta.error ? (
<div className="error">{meta.error}</div>
) : null}
</>
);
};
Then you can use the options prop to put your data accordingly

Related

React js show/hide specific element using Material UI checkbox's state

hello I'm trying to show and hide specific elements with checkbox material and what is happening now is when one checkbox is checked all the hidden divs are showing up.
You can see the problem here: https://stackblitz.com/edit/react-1ecdqb?file=demo.tsx
edit: I know that I need more variables in the state but I ask if there is a way to do it without state for each checkbox because there are gonna be 10 more checkboxes
const UninstallView = () => {
const [isChecked, setIsChecked] = useState(false);
const handleChange = event => {
if (event.target.checked) {
setIsChecked(true);
}
else {
setIsChecked(false);
}
}
return (
<div>
<FormGroup>
<FormControlLabel control={<Checkbox onChange={handleChange} />} label="simple isn't what I expected" />
{isChecked ? <TextField
id="filled-multiline-static"
label="What did you expect from simple?"
multiline
rows={4}
defaultValue=""
variant="filled"
/>
: '' }
</FormGroup>
<FormGroup>
<FormControlLabel control={<Checkbox onChange={handleChange} />} label="simple isn't working correctly" />
{isChecked ?
<div>
<h1>hello</h1>
</div>
: '' }
</FormGroup>
</div>
);
You are sharing 1 state across 2 checkboxes. You should have a separate state that holds the state for each checkbox.
This code may help:
const UninstallView = () => {
const [isFirstChecked, setIsFirstChecked] = useState(false);
const [isSecondChecked, setIsSecondChecked] = useState(false);
return (<div>
<FormGroup>
<FormControlLabel
control={<Checkbox onChange={() => setIsFirstChecked(!isFirstChecked)}/>}
label="simple isn't what I expected"/>
{isFirstChecked ? <TextField
id="filled-multiline-static"
label="What did you expect from simple?"
multiline
rows={4}
defaultValue=""
variant="filled"
/> : ''}
</FormGroup>
<FormGroup>
<FormControlLabel
control={<Checkbox onChange={() => setIsSecondChecked(!isSecondChecked)}/>}
label="simple isn't working correctly"/>
{isSecondChecked ? <div>
<h1>hello</h1>
</div> : ''}
</FormGroup>
</div>);
}
Sandbox example

Create tag using Autocomplete material UI when user input anything

I can type something which shows selected tags from the dropdown list but I want a user can type something and create a tag or multiple tags separated by a comma.
I used a useState hook which is an array.
const [tags, setTags] = useState([]);
I set the Autocomplete like the following code -
<Autocomplete
style={{ margin: "10px 0" }}
multiple
id="tags-outlined"
options={tags}
defaultValue={[]}
freeSolo
renderTags={(value, getTagProps) =>
value.map((option, index) => (
<Chip variant="outlined" label={option} {...getTagProps({ index })} />
))
}
renderInput={(params) => (
<TextField
{...params}
label="Tags"
placeholder="Tags"
value={tags}
onChange={(e) => setTags([...tags, e.target.value.split(",")])}
/>
)}
/>;
Surprisingly, I tried for an hour before questioning. But Solve it within a few moments after putting the question.
Here's the solution-
<Autocomplete
style={{ margin: "10px 0" }}
multiple
id="tags-outlined"
options={tags}
defaultValue={[...tags]}
freeSolo
autoSelect
onChange={(e) => setTags([...tags, e.target.value])}
renderInput={(params) => (
<TextField
{...params}
label="Tags"
placeholder="Tags"
value={tags}
/>
)}
/>;
the output will look like this.
user input tag
but I want to add multiple tags that didn't happen right now which can be put via giving a space or comma.
Partly relevant but I think it can be helpful for someone using React Hook Form library with MUI5.
Storing tags in the state would render them being saved in form data when submitted. Instead, you need to use their onChange function.
const Tags = ()=>{
const { control,handleSubmit } = useForm({
defaultValues: {
checkbox: true,
autocomplete:["test"]
}
});
const onSubmit = data => console.log(data);
return <>
<form onSubmit={handleSubmit(onSubmit)}>
<Controller
name="autocomplete"
control={control}
render={({ field }) => {
const {value,onChange} = field
return <Autocomplete
style={{ margin: "10px 0" }}
multiple
id="tags-outlined"
options={value}
defaultValue={[...value]}
freeSolo
autoSelect
onChange={((e)=>onChange([...value,e.target.value]))}
renderInput={(params) => {
return <TextField
{...params}
label="Tags"
placeholder="Tags"
value={value}
/>
}}
></Autocomplete>
}
}
/>
<input type="submit" />
</form>
</>
}

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

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

React Function loading initial value for Radio Group and selecting it

I am having an issue with setting the initial value of a radio group (from a value passed into the function) like this:
export default function SportTeamRanking({team, ...props}) {
So right now I have a Radio Group with 4 radio buttons and the value I want to pre-select or check is contained in the team object specified above (something like team.ranking). This value is passed from a class component into this function. So let's say the team object had a team.ranking value of "rank2" so ideally the Rank 2 radio button would be pre-selected when the function loads.
I have an initial state:
const [value, setValue] = React.useState("");
I've tried setting the React.useState(team.ranking) but still no button is selected.
I also tried using the useEffect and run it only once:
useEffect(() => {
setValue(team.ranking)
}, []);
but with no luck. I tried console logging early on and seems like the team object is still empty while all of this code is already executed?
The goal of this function is to pre-load any saved value that might have been set before (from the team object passed in), then update to a new ranking value if required and save.
For extra info here is the radio group code in the return body:
<FormControl component="fieldset">
<FormLabel component="legend">Team Ranking</FormLabel>
<RadioGroup aria-label="ranking" name="ranking1" value={value} onChange={handleChange}>
<FormControlLabel value="rank1" control={<Radio />} label="Rank 1" />
<FormControlLabel value="rank2" control={<Radio />} label="Rank 2" />
<FormControlLabel value="rank3" control={<Radio />} label="Rank 3" />
<FormControlLabel value="rank4" control={<Radio />} label="Rank 4" />
</RadioGroup>
</FormControl>
And the onChange is simply:
const handleChange = (event) => {
setValue(event.target.value);
};
Any suggestions are appreciated, thanks!
As you have mentioned that the value is passed from a class component to a function component, see if the below code answers your question.
function FormControlLabelPosition(props) {
const onChange = (e) => props.onChange(e.target.value)
return (
<FormControl component="fieldset">
<FormLabel component="legend">Team Ranking</FormLabel>
<RadioGroup aria-label="ranking" name="ranking1" value={props.value} onChange={onChange}>
<FormControlLabel value="rank1" control={<Radio />} label="Rank 1" />
<FormControlLabel value="rank2" control={<Radio />} label="Rank 2" />
<FormControlLabel value="rank3" control={<Radio />} label="Rank 3" />
<FormControlLabel value="rank4" control={<Radio />} label="Rank 4" />
</RadioGroup>
</FormControl>
);
}
export default class Parent extends React.Component {
state = {
value: "rank4"
}
handleChange = (val:string) => this.setState({value: val})
render() {
return (<FormControlLabelPosition value={this.state.value} onChange={this.handleChange}/>)
}
}
Try it out here: https://codesandbox.io/s/material-demo-4ilxb

Resources