I need to create a slightly complicated checkbox group. I was able to get this working in V5 quite easily by accessing property values within my form code. But, I don't see how to do this in V6.
I have an array of vectorDataset
s that I pass into my form using mapStateToProps
/connect
. Each vectorDataset
has an array of vectorFile
s. I create a checkbox for each vectorFile
.
My model has a list of spatialAverage
s. Each spatialAverage
has a vectorFile
. In my form, a vectorFile
checkbox should be checked if the array of spatialAverage
s contains a spatialAverage
with spatialAverage.vectorFile.id === vectorDataset.id
.
My data looks like this:
{
regions: [
{
spatialAverages: [
{
vectorFile: {
id
...
}
}
...
]
}
...
]
...
}
Here's my attempt so far...
<FieldArray name={`${region}.spatialAverages`} component={spatialAverages =>
<div className="form-section">
<h3>Spatial Averages</h3>
{vectorDatasets.map((vectorDataset, i) => (
<div key={i}>
<h4>{vectorDataset.datasetId}</h4>
{vectorDataset.files.map((vectorFile, j) =>
<div key={j} className="form-checkbox horizontal-group">
<label>
<input type="checkbox"
name="spatialAverages"
value={vectorFile.id}
/>
{vectorFile.name}
</label>
</div>
)}
</div>
))}
</div>
}/>
Any suggestions as to how I might achieve this in V6?
Hey,
I wanted to achieve something similar and ended up creating an adapter:
// adapter.jsx
import React from 'react';
import { Field } from 'redux-form';
export const CHECKBOX = 'CHECKBOX';
export const CHECKBOXES = 'CHECKBOXES';
export default function adapter(key, props) {
switch (key) {
case CHECKBOX:
return (
<input type="checkbox" {...props} />
);
case CHECKBOXES:
return (
<div>
{props.options.map((option, index) => (
<Field
key={index}
name={`${props.name}[${option.value}]`}
label={option.label}
checked={!!props.value[option.value]}
component={CHECKBOX}
/>
))}
</div>
);
default:
return null;
}
}
/// MyForm.jsx
const weekdayOptions = [
{ value: 1, label: 'Monday' },
{ value: 2, label: 'Tuesday' },
{ value: 3, label: 'Wednesday' },
{ value: 4, label: 'Thursday' },
{ value: 5, label: 'Friday' },
{ value: 6, label: 'Saturday' },
{ value: 7, label: 'Sunday' },
];
<Field
name="weekdays"
label="Weekdays"
component={CHECKBOXES}
options={weekdayOptions}
/>
@hardchor I'm trying to get a multiselect checkbox working and I'm not sure I follow everything from your example above - can you explain the workflow in a bit more detail? I'm still very new to redux and redux-form and am trying to get some sort of multi-select option working one of my forms but I've spent the better part of 2 days and don't have it functional yet...
I would avoid this method, it create far more connected Field
components then is needed. Each state update will re-render all the fields for each checkbox AND run your redux reducers.
better to use a single Field
component and manage the state of child <input type="checkbox" />
components within that custom Field
component.
@andrewmclagan can you provide example please? :)
import React from 'react';
import { FormGroup, ControlLabel, HelpBlock, Label } from 'react-bootstrap';
const Error = ({ meta : {touched, error} }) => (touched && error ? <HelpBlock>{error}</HelpBlock> : null);
const CheckboxGroup = ({ label, required, name, options, input, meta}) => (
<FormGroup controlId={name}>
<ControlLabel>{label} { required && <Label bsStyle="info">required</Label> }</ControlLabel>
{ options.map((option, index) => (
<div className="checkbox" key={index}>
<label>
<input type="checkbox"
name={`${name}[${index}]`}
value={option.name}
checked={input.value.indexOf(option.name) !== -1}
onChange={event => {
const newValue = [...input.value];
if(event.target.checked) {
newValue.push(option.name);
} else {
newValue.splice(newValue.indexOf(option.name), 1);
}
return input.onChange(newValue);
}}/>
{option.name}
</label>
</div>))
}
<Error meta={meta} />
</FormGroup>
);
export default CheckboxGroup;`
@Crazy-Ivan may I ask for an example on how would you implement this if I will put this in a separate component so I can reuse this
I think @Crazy-Ivan comment should become part of the documentation.
@Crazy-Ivan 's answer works this means onChange
mutates the state before it dispatches the action?
me thinks that would be VERY beneficial to have in the docs, along with a modified version of Ivan's answer
@goldylucks @aftabnaveed @Crazy-Ivan
If any of you would like to contribute to this project, that's a chance 馃槈 send us your docs PR!
@gustavohenke
I might take a swing at it with some guidance.
I'm using a simplified version of @Crazy-Ivan code.
Seeing how many people waste hours on a simple thing as checkboxes, how about I make a component for that?
with documentations, tests and everything of course :)
@goldylucks Sounds like a plan, I'm currently wasting hours, too :)
Ah, code changes aren't exactly what I was "hired" for, @goldylucks 馃榿
@erikras is the best person to ask some suggestion, but he might take ages to look at this issue, so perhaps you should ping him on Twitter!
Anyway, I support your idea! 馃憤
If you could make sure that your component is not opinionated in terms of what the UI should look like, then IMO it's definitely an addition to the next release.
@gustavohenke
I'm not on twitter :o
The component will be fully customizable, and will support passing className
and style
for every part, so no worries about that.
For those who spent hours struggling with this,
here comes the rescue which modified from @Crazy-Ivan's
import React, {Component} from 'react';
import {Field} from "redux-form";
import PropTypes from 'prop-types';
export default class CheckboxGroup extends Component {
static propTypes = {
options: PropTypes.arrayOf(PropTypes.shape({
label: PropTypes.string.isRequired,
value: PropTypes.string.isRequired
})).isRequired
};
field = ({input, meta, options}) => {
const {name, onChange} = input;
const {touched, error} = meta;
const inputValue = input.value;
const checkboxes = options.map(({label, value}, index) => {
const handleChange = (event) => {
const arr = [...inputValue];
if (event.target.checked) {
arr.push(value);
}
else {
arr.splice(arr.indexOf(value), 1);
}
return onChange(arr);
};
const checked = inputValue.includes(value);
return (
<label key={`checkbox-${index}`}>
<input type="checkbox" name={`${name}[${index}]`} value={value} checked={checked} onChange={handleChange} />
<span>{label}</span>
</label>
);
});
return (
<div>
<div>{checkboxes}</div>
{touched && error && <p className="error">{error}</p>}
</div>
);
};
render() {
return <Field {...this.props} type="checkbox" component={this.field} />;
}
}
And this is how you use it
const options = [
{label: 'English', value: 'en'},
{label: '绻侀珨涓枃', value: 'zh-TW'},
{label: 'Tibetan', value: 'bo'}
];
<CheckboxGroup name="langs" options={options} />
@kmsheng : I tried your solution and it works great, only touched
is never updated because input.onBlur
is not connected. I found it works best when putting it right before return onChange(arr);
, like this:
import React, {Component} from 'react';
import {Field} from "redux-form";
import PropTypes from 'prop-types';
export default class CheckboxGroup extends Component {
static propTypes = {
options: PropTypes.arrayOf(PropTypes.shape({
label: PropTypes.string.isRequired,
value: PropTypes.string.isRequired
})).isRequired
};
field = ({input, meta, options}) => {
const {name, onChange, onBlur, onFocus} = input;
const {touched, error} = meta;
const inputValue = input.value;
const checkboxes = options.map(({label, value}, index) => {
const handleChange = (event) => {
const arr = [...inputValue];
if (event.target.checked) {
arr.push(value);
}
else {
arr.splice(arr.indexOf(value), 1);
}
onBlur(arr);
return onChange(arr);
};
const checked = inputValue.includes(value);
return (
<label key={`checkbox-${index}`}>
<input type="checkbox" name={`${name}[${index}]`} value={value} checked={checked} onChange={handleChange} onFocus={onFocus} />
<span>{label}</span>
</label>
);
});
return (
<div>
<div>{checkboxes}</div>
{touched && error && <p className="error">{error}</p>}
</div>
);
};
render() {
return <Field {...this.props} type="checkbox" component={this.field} />;
}
}
Hello, how a can return this in checkbox group
value: {
values: ['one', 'two'],
labels: ['One apple', 'Two apples'],
}
@jorrit : I tried your code, the checkboxes don't get unticked or unticked when they are infocus. The first time it works, but if we try again without removing focus, the checkbox doesn't get unticked and the value is added to the array the second time. Would you know why?
Edit: Removing the call to OnBlur from handleChange makes it work well.
@Jaikant which browser is this? Are you using keyboard navigation to check/uncheck?
@jorrit I am using chrome on Mac with a mouse.
I don't know how I can help you. If the first example works for you, I guess you should use it.
I needed the same, but needed to extend the visualisation with custom components. In my use-case the choices also need to display images. And another one had to display images in another way.
So I created a package for this, based on @kmsheng 's example, located here: https://github.com/leroy0211/form-redux-checkbox-group
It's far from ready yet, and also not available on NPM. So please contribute!
@Crazy-Ivan
Happened to see your answer after Googling for a bit for Group checkbox. Thanks a ton! Got what I needed to work exactly. :)
I was hoping to achieve this without interfering with the onChange handler, here's how I did it if it helps anyone:
I am using redux-form-material-ui
too
[
{ id: 'watermelon', label: 'I like watermelon.' },
{ id: 'pineapple', label: 'I like pineapple.' },
{ id: 'banana', label: 'I like bananas.' },
].map(food => (
<FormControlLabel
label={food.label}
control={
<Field
name="favouriteFoods"
component={Checkbox}
normalize={(value, previousValue) => {
if (value) {
if (previousValue.includes(food.id)) return previousValue;
return [...previousValue, food.id];
}
const i = previousValue.indexOf(food.id);
if (i === -1) return previousValue;
return [
...previousValue.slice(0, i),
...previousValue.slice(i + 1),
];
}}
format={value => value.includes(food.id)}
/>}
/>
))
@kmsheng how can I handle the function to show preselected checkboxes ?
const checkboxes = options.map(({ label, value, check }, index) => {
var execution = false
var checked = check
const handleChange = (event) => {
const arr = [...inputValue];
if (event.target.checked) {
arr.push(value);
}
else {
arr.splice(arr.indexOf(value), 1);
}
checked = !checked
return onChange(arr);
};
return (
<FormControlLabel
control={<Checkbox name={`${name}[${index}]`} value={value} checked={checked} onChange={handleChange} />}
label={label}
/>
);
});
but I am getting this error
TypeError: Invalid attempt to spread non-iterable instance
How can I get this to work with semantic-ui's Checkbox? I'm using redux-form, and to my understanding I need to hook up the component so I can read the value, using onChange and value like so:
onChange={(param, data) => props.input.onChange(data.value)}
value={props.input.value}
I've tried but I don't really understand how to combine this with the group code...
How about "check all/uncheck all" input, help me pls ?
@jorrit Hi! How i can set checked
for existing checkbox from outside?
@Crazy-Ivan 's answer works this means
onChange
mutates the state before it dispatches the action?me thinks that would be VERY beneficial to have in the docs, along with a modified version of Ivan's answer
Yes the user deployed some underground tech. I am not sure how to feel about it. There was also some intricate destructuring involved with the function params as well. Some serious cheese here,
@kmsheng
For those who spent hours struggling with this,
here comes the rescue which modified from @Crazy-Ivan'simport React, {Component} from 'react'; import {Field} from "redux-form"; import PropTypes from 'prop-types'; export default class CheckboxGroup extends Component { static propTypes = { options: PropTypes.arrayOf(PropTypes.shape({ label: PropTypes.string.isRequired, value: PropTypes.string.isRequired })).isRequired }; field = ({input, meta, options}) => { const {name, onChange} = input; const {touched, error} = meta; const inputValue = input.value; const checkboxes = options.map(({label, value}, index) => { const handleChange = (event) => { const arr = [...inputValue]; if (event.target.checked) { arr.push(value); } else { arr.splice(arr.indexOf(value), 1); } return onChange(arr); }; const checked = inputValue.includes(value); return ( <label key={`checkbox-${index}`}> <input type="checkbox" name={`${name}[${index}]`} value={value} checked={checked} onChange={handleChange} /> <span>{label}</span> </label> ); }); return ( <div> <div>{checkboxes}</div> {touched && error && <p className="error">{error}</p>} </div> ); }; render() { return <Field {...this.props} type="checkbox" component={this.field} />; } }
And this is how you use it
const options = [ {label: 'English', value: 'en'}, {label: '绻侀珨涓枃', value: 'zh-TW'}, {label: 'Tibetan', value: 'bo'} ]; <CheckboxGroup name="langs" options={options} />
This worked like champ. Thank you.
@Crazy-Ivan, the CheckBoxGroup component is good but in my case I need it in Multi-select-box form. can you or any help in this?
I am struggling for hours with it to work in redux-form!!
Most helpful comment