Cannot get Material UI radio buttons to work with Formik - reactjs

I am trying to use Material UI radio buttons with Formik, and they are not clicking properly. I've reduced the problem to the following example: https://codesandbox.io/s/amazing-currying-s5vn0
If anyone knows what I might be doing wrong, or if there is a bug in either system, then please let me know. When clicking on the buttons in the above example, they do not stay clicked. I have a more complex react functional component that uses other library components, so I cannot include it here. It is behaving a little differently: the buttons stay clicked even after clicking a different button. It may or may not be the same issue. Thanks in advance.

You need to be rendering the radio buttons inside of the FormikRadioGroup component you are rendering as a Formik Field. That way you can actually pass the props being managed by Formik down to the components to be used, as well as allow the RadioGroup component to make sure only one button is clicked at a time. I added an options prop to provide a way to pass an array of radio options and removed all of the elements you were rendering outside of that component:
const FormikRadioGroup = ({
field,
form: { touched, errors },
name,
options,
...props
}) => {
return (
<React.Fragment>
<RadioGroup {...field} {...props} name={name}>
{options.map(option => (
<FormControlLabel value={option} control={<Radio />} label={option} />
))}
</RadioGroup>
{touched[fieldName] && errors[fieldName] && (
<React.Fragment>{errors[fieldName]}</React.Fragment>
)}
</React.Fragment>
);
};
Fork here.
EDIT: Updated the sandbox with an alternate example using a render function as a child within the Field component.

import { FormControlLabel, Radio, LinearProgress } from '#material-ui/core';
import { Formik, Field } from 'formik';
import { RadioGroup } from 'formik-material-ui';
<Formik {...otherProps}>
{({ isSubmitting }) => (
<Field component={RadioGroup} name="activity">
<FormControlLabel
value="painting"
control={<Radio disabled={isSubmitting} />}
label="Painting"
disabled={isSubmitting}
/>
<FormControlLabel
value="drawing"
control={<Radio disabled={isSubmitting} />}
label="Drawing"
disabled={isSubmitting}
/>
<FormControlLabel
value="none"
control={<Radio disabled={isSubmitting} />}
label="None"
disabled
/>
</Field>
)}
</Formik>;
Now this is documented! Using RadioGroup from formik-material-ui.
https://stackworx.github.io/formik-material-ui/docs/api/material-ui/

Related

React-Hook-Form conditional fields in a map function

I'm curious if anyone has used conditional fields in react-hook-from but within a map function. I've got the basic rendering happening, but since it is in a list and they all refer to the same .map() criteria, it is populating the conditional input for all fields, regardless if they are checked. Heres the basic idea for using conditional fields in RHF: https://codesandbox.io/s/react-hook-form-conditional-fields-qgr41
Heres what I've got so far:
{goals && goals.map((goal) => (
<>
<FormControlLabel
control={
<Switch
key={goal.title}
//id..? marked? title?
{...register('marked')}
name='marked'
value={goal.marked}
/>}
label={goal.title}
/>
<br />
{marked && (
<>
<Input
key={goal.title}
{...register('goal.note')}
id="note"
type='text'
label="Progress Note"
name="note"
onChange={(e) => {
e.target.value = e.target.value
}}
/>
<br />
</>
) }
The problem is that from the UI side, when I toggle the switch (or mark a checkbox), that all the fields populated in the map are referring to the same prop in the switch, 'marked'. So when toggling for 1, it populates the additional input field for all elements in the map function.
An additional issue I have yet to look into is passing the data for the individual fields to the data collected by the form for the submit. Right now, my assumption is that all fields would be treated as one value, since the form is recognizing them all together. So ideally if one item in the map is toggled and additional info is provided, then only those values are passed to the form for the submit, rather than all fields (toggled or not) being passed.
UPDATE
So I got the toggle functioning by creating a child component in order to more easily manage the state of the toggle, and leveraging the index within the props so the browser would not treat each conditional field as the same:
parent component:
{goals && goals.map((goal, index) => (
<GoalInput
goal={goal}
index={index}
register={register}
control={control}
errors={errors}
/>
new child component:
function GoalInput({ goal, index, register, control, errors }) {
const [toggle, setToggle] = useState(false)
return (
<>
<FormControlLabel
control={
<Switch
key={index}
{...register(`goals.${index}.marked`)}
checked={toggle}
name={`goals.${index}.marked`}
value={toggle}
onChange={() => setToggle(!toggle)}
/>
}
label={goal.title}
/>
<br />
{toggle ? (
<>
<Controller
control={control}
name={`goals.${index}.note`}
id={`goals.${index}.note`}
render={({field}) => (
<Input
type='text'
index={index}
error={!!errors.note}
value={field.value}
onChange={(e)=>field.onChange(e)}
label="Progress Note"
/>
)}
/>
<br />
</>
) : <></>}
</>
)
}
So the toggles work independently, appropriately record whether the value of the toggle is true/false, and the subsquent conditional input in being tracked as well. Still have some struggles with the data being passed correctly through to the backend, but that is another issue. Hope this helps anyone coming along.

How to use material UI Checkbox with formik?

I am not able to code in the right way such that formik.values will reflect into Material UI's Checkbox
Checkboxes are a little tricky to include in third party forms packages, and Material UI doesn't help with that either.
What you need to do is use the FormControlLabel component to handle the onChangge event that formik needs. Then on the Checkbox you just set the checked prop to be whatever formik has in its values.
Here is an example I use for a Checkbox to set an isAdmin value.
const formik = useFormik({
initialValues:{
isAdmin: false
},
});
<FormControlLabel
control={<Checkbox checked={formik.values.isAdmin} />}
label="is Admin"
name="isAdmin"
onChange={formik.handleChange}
/>
Using 'as'
Use the as prop with Formik Field and pass any props required to the Field itself. It will pass them onto FormControlLabel. For example, here the label field is passed down to FormControlLabel:
<Field
type="checkbox"
name="myFormikName"
as={FormControlLabel}
control={<Checkbox />}
label="This will trigger my formik name field"
/>
Multiple checkboxes using 'as'
If you have multiple checkboxes with the same name (array) you need to pass the "checked" value like so. Checks if the Formik 'name' array includes the name.
const { values } = useFormikContext()
render (
{names?.map(name => (
<Field
type="checkbox"
name="names"
value={name.fullName}
key={name.id}
as={FormControlLabel}
control={<Checkbox/>}
checked={values.names.includes(name.fullName)}
label={name.fullName}
/>
))}
)
Using 'setFieldValue'
const { values, setFieldValue } = useFormikContext()
<FormControlLabel
control={<Checkbox />}
label="My mui label"
checked={values.myMuiCheck}
onChange={() =>
setFieldValue(
'myMuiCheck',
!values.myMuiCheck,
)
}
/>

How to make smaller radio buttons

I can't find a way to create a group of three small (19px high) labelled radio buttons in material-ui. I'm migrating a browser app from static HTML/JS into material-ui/react, and I need the group to stay the same size.
The button group I'm starting with has three small vertically aligned labelled radio buttons. Here's
a screenshot:
Here's the code for the best I've been able to do using the material-ui documentation:
<FormControl
margin="dense"
size="small"
>
<RadioGroup
size="small"
name="dataSource"
value={dataSource}
onChange={handleDataSourceChange}
>
<FormControlLabel
value="CaseCount"
control={
<Radio
className={classes.radio}
size="small"
/>
}
label="Cases"
/>
<FormControlLabel
value="DeathCount"
control={
<Radio
className={classes.radio}
size="small"
/>
}
label="Deaths"
/>
<FormControlLabel
value="HotSpots"
control={
<Radio
size="small"
/>
}
label="HotSpot warning"
/>
</RadioGroup>
</FormControl>
This results in an oversized group -- the button icons are too large, the text is too large, and each row is 38 pixels high (twice the 19 pixels of the original). Here's a screenshot of the material-ui counterpart:
I think I want the result to use a vanilla html input (with type of radio) instead of an svg icon, and a font with font-height of 13px. How do I do that in material-ui?
I know how to fix the padding-left so I'm not worried about that. How do I get rows that are 19px high instead of 38px?
Reason
If you inspect the Material radio button, we can see that the padding of the radio button is causing each row to have a height of 38px.
Code
Therefore, we can simply remove the vertical padding from all radio buttons with the following code (note that my code is slightly different than yours):
const useStyles = makeStyles({
// Applied to <Radio />
root: {
width: 19,
height: 19,
paddingTop: 0,
paddingBottom: 0,
},
// Applied to <FormControlLabel />
label: {
fontSize: 13
}
});
export default function RadioButtonsGroup() {
const [value, setValue] = React.useState("CaseCount");
const classes = useStyles();
const handleChange = (event) => {
setValue(event.target.value);
};
const customRadio = <Radio size="small" classes={{root: classes.root}} />;
return (
<FormControl component="fieldset">
<RadioGroup value={value} onChange={handleChange}>
<FormControlLabel classes={{label: classes.label}} control={customRadio} value="CaseCount" label="Cases" />
<FormControlLabel classes={{label: classes.label}} control={customRadio} value="DeathCount" label="Deaths" />
<FormControlLabel
classes={{label: classes.label}}
control={customRadio}
value="HotSpots"
label="Hotspot Warning"
/>
</RadioGroup>
</FormControl>
);
}
Explanation
According to the Radio API documentation, we can apply custom styling to root of the radio button by overriding styles with classes using the class name of root. Therefore, we first define a style object called root in the makeStyles() function. Next, we apply the styling to the Radio component by adding the prop of classes={{root: classes.root}}:
<Radio size="small" classes={{root: classes.root}} />
Similarly, according to the FormControlLabel API documentation, we can apply custom styling to the text label using the label class name. Therefore, we first define a style object of label in the makeStyles() function. Next, we apply the styling to the FormControlLabel component by adding the prop of classes={{label: classes.label}}:
<FormControlLabel classes={{label: classes.label}} /* ... */ />

Cannot display switch in tabs in material-ui

I need my tabs to have a tab consisting of a switch but when I implemented it as it was said, I am not able to see it there.
Maybe it's hidden underneath but I tried changing its z-index.
Pass your Switch component as the label prop.
...
<Tab
component="span"
label={
<Switch
checked={isSwitchOn}
onChange={(e) => setSwitch(!isSwitchOn)}
name="toggleType"
/>
}
/>
Tab component does not display children props inside the MuiTab-wrapper element.
Maybe you can use icon props.
<Tab
component="span"
icon={
<Switch
checked={isSwitchOn}
onChange={(e) => setSwitch(!isSwitchOn)}
name="toggleType"
/>
}
/>

Better way to generate a component array in a React reusable component

I am building a reusable component for radio buttons. Naturally, it is unpredictable to know how many radios it will be needed in each case, so some how that has to be mapped in an array. Also, the component needs to respect the HTMl structure that it will render in and the RadioGroup and Radio buttons must be in the same component for scope purposes.
I have it working this way:
Component
const CMRadioGroup = (props, context) => {
return (
<RadioGroup
name={props.name}
style={{flexDirection: "row"}}
className={props.className}
value={props.value}
onChange={props.onChange}>
{props.radios.map(radio =>
<FormControlLabel
key={radio.value}
value={radio.value}
style={{marginRight: 40, height:30}}
control={<Radio />}
label={context.t(radio.label)} />,
)}
</RadioGroup>
);
};
Usage
<CMRadioGroup
name="select"
value={this.state.select}
onChange={this.handleChangeSelect}
//I don't like the part below
radios={[
{
value:"radio1",
label:"My First Radio",
},
{
value:"radio2",
label:"My Second Radio",
},
]}/>
}
It would prefer if instead of using it as a json array, that they were functional items that can be propped with functions and other props such as colors, onChange, etc. Something like this for example:
<CMRadioGroup
name="select"
value={this.state.select}
onChange={this.handleChangeSelect}
//This is better
<Radio
color="primary"
value="radio1",
label="My First Radio"/>
<Radio
color="primary"
value="radio2",
label="My Second Radio"/>
...
<Radio
color="secondary"
value="radio-nth",
label="My Nth Radio"/>
]}/>
}
Thanks in advance guys
What I can interpret is you want to pass radio buttons as children to component. you can easilt pass them as a clild. See example.
<CMRadioGroup
name="select"
value={this.state.select}
onChange={this.handleChangeSelect}
>
<Radio
color="primary"
value="radio1",
label="My First Radio"/>
<Radio
color="primary"
value="radio2",
label="My Second Radio"/>
<Radio
color="secondary"
value="radio-nth",
label="My Nth Radio"/>
</CMRadioGroup>
Do not pass them as props. The only drawback of this approach is that the component will rerender everytime of state change as children are treated as objects and react shallow compares the props (when using React.PureComponent).

Resources