New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Material UI + multiple checkboxes + default selected #1517
Comments
can you update your example with |
Done, not sure how is the correct way though, since that would answer my question :) |
This is probably a more relevant question for MUI's checkbox, but i just quicky did it with my own understanding. https://codesandbox.io/s/serverless-dream-jm134?file=/src/App.js |
@bluebill1049 I am not sure if it's MUI checkbox question. |
I have to check the doc for MUI: |
I am trying to do something like this:
But no luck, |
With chakra ui and formik that would be like this: https://codesandbox.io/s/reverent-goldstine-1bvq9?file=/src/App.js |
maybe you should try to use chakra with RHF. |
Here is a working version:
codesandbox link: https://codesandbox.io/s/material-demo-65rjy?file=/demo.js:0-932 However, I do not recommend doing so, because Checkbox in material UI probably should return checked (boolean) instead of (value). |
I'd like to post another solution as well: import React, { useState } from "react";
import { useForm, Controller } from "react-hook-form";
import FormControlLabel from "@material-ui/core/FormControlLabel";
import Checkbox from "@material-ui/core/Checkbox";
export default function CheckboxesGroup() {
const defaultNames = ["bill", "Manos"];
const { control, handleSubmit } = useForm({
defaultValues: { names: defaultNames }
});
const [checkedValues, setCheckedValues] = useState(defaultNames);
function handleSelect(checkedName) {
const newNames = checkedValues?.includes(checkedName)
? checkedValues?.filter(name => name !== checkedName)
: [...(checkedValues ?? []), checkedName];
setCheckedValues(newNames);
return newNames;
}
return (
<form onSubmit={handleSubmit(e => console.log(e))}>
{["bill", "luo", "Manos", "user120242"].map(name => (
<FormControlLabel
control={
<Controller
as={<Checkbox />}
control={control}
checked={checkedValues.includes(name)}
name="names"
onChange={() => handleSelect(name)}
/>
}
key={name}
label={name}
/>
))}
<button>Submit</button>
</form>
);
} Codesandbox link: https://codesandbox.io/s/material-demo-639rq?file=/demo.js |
i love solutions! thanks very much @4ortytwo |
@bluebill1049 The initial states of the checkboxes break on the latest version. They are checked but their ui state is not updated. Try to switch from 5.6.1 and 5.6.0 and refresh on that example: |
@mKontakis can you raise this as an issue? |
wait didn't i supply you with a working version? (above) |
You did mate, but I preferred to use that solution https://stackoverflow.com/a/61541837/2200675 to transform the values using Yup instead of modifying the returned value of Material's component CheckBox. Although 5.6.1 breaking that solution may be a bug that should be fixed? I checked the changes between 5.6.1 and 5.6.0 I didn't manage to find something relevant |
right, please create a separate issue I will do some investigation tonight. |
Hey I was able to get almost the result I was looking for with @4ortytwo solution. Everything is working except for the The only way I can get this working is to stop using Material ui Checkbox and use a basic `. Just need to match styling now 😞 Are there any ideas on how to get the reset functionality to work with @4ortytwo solution? Here is a modified codesandbox which simulates what my setup is doing when fetching data with Apollo and reset in the Also note I am using this code to get the initial list value as the data comes from Apollo client const initialList = R.path(['defaultValuesRef', 'current', name])(control)
useEffect(() => {
if (initialList) {
setCheckedList(initialList)
}
}, [initialList]) And here is the full component for reference, note the const CourseAvailableTo = (props) => {
const {
control,
name,
error,
className,
darkDisabled,
required,
disabled,
checkboxes,
label,
headerLabel,
register,
} = props
const classes = useStyles()
const hasError = !!error
const [checkedList, setCheckedList] = useState([])
const initialList = R.path(['defaultValuesRef', 'current', name])(control)
useEffect(() => {
if (initialList) {
setCheckedList(initialList)
}
}, [initialList])
const handleChange = useCallback(
(value) => {
const onList = checkedList.includes(value)
const oldList = [...checkedList]
let newList = [...oldList]
if (onList) {
// remove from list
newList = R.reject(R.equals(value))(oldList)
} else {
// add to list
newList = R.append(value)(oldList)
}
setCheckedList(newList)
// control.setValue(name, newList)
return newList
},
[checkedList]
)
return (
<div className={clsx(classes.wrapper, className)}>
<Typography variant={headerLabel} gutterBottom>
{`${label}:${required ? ' *' : ''}`}
</Typography>
<FormControl component="fieldset" className={classes.checkboxFormControl}>
<FormGroup error={hasError} className={classes.checkboxFormGroup}>
{checkboxes.map((checkbox, index) => (
<FormControlLabel
className={clsx(
classes.checkbox,
darkDisabled && classes.darkDisabledCheckBoxLabel
)}
key={checkbox.id}
control={
<Controller
as={<Checkbox />}
className={clsx(darkDisabled && classes.darkDisabledCheckBox)}
disabled={disabled}
value={checkbox.value}
onChange={(e) => handleChange(checkbox.value)}
checked={checkedList.includes(checkbox.value)}
name={`${name}`}
control={control}
/>
// <input
// type="checkbox"
// className={clsx(darkDisabled && classes.darkDisabledCheckBox)}
// disabled={disabled}
// name={name}
// value={checkbox.value}
// ref={register}
// />
}
label={checkbox.label}
/>
))}
</FormGroup>
<FormHelperText error={hasError} className={classes.errorMessage}>
{hasError && error}
</FormHelperText>
</FormControl>
<Button
type="button"
className={classes.selectAllButton}
variant="outlined"
color="primary"
disabled={disabled}
onClick={() => {
if (R.length(checkedList) <= R.length(checkboxes) - 1) {
control.setValue(name, R.pluck('value')(checkboxes))
return setCheckedList(R.pluck('value')(checkboxes))
}
control.setValue(name, [])
return setCheckedList([])
}}
>
{`${
R.length(checkedList) <= R.length(checkboxes) - 1
? 'Select All'
: 'Deselect All'
}`}
</Button>
</div>
)
} |
@natac13 , your I added this: const handleReset = () => {
setCheckedValues(defaultNames);
reset();
}; and called it here <button onClick={handleReset}>Reset</button> See the codesandbox, it is working there: https://codesandbox.io/s/material-demo-0bwsi?file=/demo.js |
That works! Thanks @4ortytwo. However I would have to move the Again thank you for the solution which works great! However I think I may just make a custom checkbox component which using Also this library is amazing! No re-renders on inputs! Just hard to use Material ui. At least that is what I am finding moving from Formik. |
V6 will make this exp easier, with render props (Controller). |
For anyone looking at this with v6.x.x, the workarounds here do not work at all in v6.01 currently |
use |
This solution works with v6 now. |
Thanks for sharing @4ortytwo |
Again, issues with this use-case on v6.0.8. Let's recap: Let's say that I have an array of objects as such:
Each object corresponds to one checkbox. The user should be able to select multiple and get a result in the form of: I am trying to use the I tried some workarounds but nothing works, the array is not populated/removed properly. Here is the codesandbox: https://codesandbox.io/s/material-demo-2wvuq?file=/demo.js @bluebill1049 Maybe you have an opinion on that? |
@mKontakis, I'm not Bill but came up with a couple of solutions. With and without Adjusted your Yup resolver as well, it filters out falsy fields now but would transform your values into an array of indices otherwise. WITHOUT https://codesandbox.io/s/material-demo-bzj4i?file=/demo.js WITH https://codesandbox.io/s/material-demo-ofk6d?file=/demo.js With |
Thanks @4ortytwo, @mKontakis i will take a look at this one. maybe consider staying at the old version (if works for you) until i get you a solution. |
wow @4ortytwo your example works great, i think it would probably easier to build a controlled component which wrapt those checkbox and a single Controller just to collect |
Thanks @bluebill1049, adjusted both examples and wrapped the checkboxes with a Interested to know whether you meant it this way. Without With |
nice @4ortytwo yeah! personally I think it's cleaner with a single |
It is cleaner, indeed. My pleasure, @bluebill1049! Thanks for the great library! :) |
@4ortytwo @bluebill1049 Hello, Thanks for the solutions with MuiCheckboxes without useState. How can we do validation without yup library by just providing rules from RHF ? |
you can use the validate function right? |
@bluebill1049 Yes. We can add validate function but how can we attach custom message to that ? |
what do you mean custom message @sandeepkumar-vedam-by? can't you produce different messages based on the selection from |
@bluebill1049 Here is the validate function and when value length is
Generally, we can add message to rules like below
|
you can have a switch statement inside right? based on your array value return specific error message. |
@bluebill1049 Yes. It is working. I didn't think that we can directly return message in validate function. Thanks :) |
If not asking too much, could you do your examples with Typescript @4ortytwo ? It's almost there, the render function complains here about this:
I'm unsure how to fix it. It looks like the return from render is expected to be something else than an array so when you put the array there, the type won't validate. EDIT: I do think it's a problem with the |
I also got this same error, this is because the render prop does not allow an array of JSX elements. So I just wrapped my mapped JSX with a fragment. This managed to "fool" typescript |
I just threw a Nice fix too @ahmedrafayat This is my final element: <Controller
name="ids"
control={control}
// @ts-ignore
// <Controller /> render doesnt expect an array of elements (JSX.Element[])
// BUT it works regardless. So I'm ignoring TS typecheck here until it's fixed
render={() =>
arrayOfElements.map((element) => (
<FormControlLabel
control={
<Checkbox
onChange={() => handleCheck(element.id)}
checked={checkedValues.includes(element.id)}
/>
}
key={element.id}
label={element.action}
/>
))
}
/> |
Could anybody tell me how to get the reset to work properly within @4ortytwo's latest "Without Or at least how to get the checkboxes to reflect the reset form state? https://codesandbox.io/s/material-demo-forked-9ekb9?file=/demo.js:2171-2214 |
Here is a solution I've come up with, @chase2981 : you'll need to swap props.value.includes(item.id) so the checkbox is checked only if it was actually checked or initially set. Resetting works as well. Hope it helps. Check the link below |
Hi everyone I don't know if it could still help someone but I find a solution . That's not necessarily a best practice but i made it work like this: <Controller
key={inputIndexCounter}
name={`patientAnswers[${inputIndexCounter}]`}
defaultValue={currentPatientRegistry.patientAnswers[currentPatAnswersIdx]}
control={control}
render={(props) => (
<SpCheckbox
formControlStyle={formControlStyleCheck}
style={{ marginTop: 15 }}
checked={props.value}
onChange={(e) => {
if (e.target.checked) {
props.onChange(questionAnswer.id);
} else {
props.onChange(e.target.checked);
}
}}
rightlabel
variant='column'
label={questionAnswer.answer}
></SpCheckbox>
)}
/> |
import FormControlLabel from '@material-ui/core/FormControlLabel';
import Switch, {SwitchProps} from '@material-ui/core/Switch';
import React from 'react';
import {useController, UseControllerOptions} from 'react-hook-form';
interface Props extends SwitchProps {
control: UseControllerOptions['control'];
rules?: UseControllerOptions['rules'];
label: React.ReactNode;
}
const ControlledSwitch: React.VFC<Props> = ({control, name, rules, label, ...switcherProps}) => {
const {
field: {ref, onChange, ...inputProps},
} = useController({
name,
control,
rules,
defaultValue: false,
});
return (
<FormControlLabel
className="m-0"
control={
<Switch
{...inputProps}
{...switcherProps}
inputRef={ref}
checked={value}
onChange={(e) => onChange(e.target.checked)}
/>}
label={<span className="font-xs">{label}</span>}
/>
);
}
export default ControlledSwitch; |
Here's my solution without additional state and without a separate handler function. I use Lodash's <Controller
name="topics"
control={control}
defaultValue={[]}
render={({ field: { value, onChange, ...inputProps }, fieldState: { error } }) => (
<>
{topics.map((topic) => (
<FormControlLabel
key={topic}
label={topic}
control={
<Checkbox checked={value?.includes(topic)} onChange={() => onChange(_.xor(value, [topic]))} />
}
/>
))}
</>
)}
/> |
why was this issue closed? |
Material UI + React Hook Form + Yup .
// 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 |
I am trying to build a form which accommodates multiple 'grouped' checkboxes using Material UI.
The checkboxes are created async from a HTTP Request.
I want to provide an array of the objects IDs as the default values:
defaultValues: { boat_ids: trip?.boats.map(boat => boat.id.toString()) || [] },
Also, when I select or deselect a checkbox, I want to add/remove the ID of the object to the
values
of react-hook-form.How can I achieve that?
Here is a sample that I am trying to reproduce the issue:
https://codesandbox.io/s/smoosh-dream-zmszs?file=/src/App.js
Right now with what I have tried, I get this:
Bonus point, validation of minimum selected checkboxes using
Yup
boat_ids: Yup.array() .min(2, ""),
Thanks in advance!
The text was updated successfully, but these errors were encountered: