I am using react antd . I have got array of objects that's groupKey .I am mapping checkbox by using Groupkey and also I have got two different types of checkbox . One is Select All Checkbox . it actually works when user click on the Select All or User select on all individual checkbox . Other is individual checkbox , user can Select on individually . when user submit on Button , then it's give me this data format ["manage_books","manage_journals","manage_deals"]
here is my trying code :
let defaultCheckedList = ["manage_deals"];
state = {
groupKey: [{
id: 1,
key: "manage_books",
label: "books"
},
{
id: 2,
key: "manage_journals",
label: "journals"
},
{
id: 3,
key: "manage_deals",
label: "deals"
}
],
checkedList: defaultCheckedList,
output: [],
indeterminate: true,
checkAll: false
};
onCheckAllChange = e => {
this.setState({
checkedList: e.target.checked ?
this.state.groupKey.map(item => item.key) :
[],
indeterminate: false,
checkAll: e.target.checked
});
};
onChange = (e, value) => {
this.setState({
checked: e.target.checked,
output: this.state.output.concat(value)
});
};
onSubmit = () => {
console.log(this.state.output)
}
render(UI)
<
div >
<
div className = "site-checkbox-all-wrapper" >
Select All <
Checkbox
indeterminate = {
this.state.indeterminate
}
onChange = {
this.onCheckAllChange
}
checked = {
this.state.checkAll
}
/> <
/div>
I am looping checkbox by groupKey.I am passing key using onChange method. {
this.state.groupKey.map(item => ( <
div className = "userpermission-content"
key = {
item.id
} > {
item.label
} <
Checkbox onChange = {
(e, value) => this.onChange(e, item.key)
}
value = {
item.key
}
/>{" "} <
/div>
))
} <
button onClick = {
this.onSubmit
} > submit < /button> <
/div>
);
}
}
In this code, you can see that two individual checkbox is initial select, I need to get completely like this: https://codesandbox.io/s/4k6qi
this is my codesanbox: https://codesandbox.io/s/check-all-ant-design-demo-vhidd?file=/index.js
Here is what I have come up with
https://codesandbox.io/s/check-all-ant-design-demo-6cm2v?file=/index.js
import React from "react";
import ReactDOM from "react-dom";
import "antd/dist/antd.css";
import "./index.css";
import { Checkbox } from "antd";
const CheckboxGroup = Checkbox.Group;
class App extends React.Component {
state = {
groupKey: [
{ id: 1, key: "manage_books", label: "books" },
{ id: 2, key: "manage_journals", label: "journals" },
{ id: 3, key: "manage_deals", label: "deals" }
],
checked: {},
output: [],
indeterminate: true,
checkAll: false
};
onCheckAllChange = e => {
const { groupKey } = this.state;
const checked = groupKey.reduce((prev, curr) => {
return { ...prev, [curr.key]: e.target.checked };
}, {});
this.setState({ checked, checkAll: e.target.checked });
};
checkAll = () => {};
onChange = (e, value) => {
// this.setState({
// checked: e.target.checked,
// output: this.state.output.concat(value)
// });
this.setState(
state => ({
checked: { ...state.checked, [value]: e.target.checked }
}),
() => {
const { checked, groupKey } = this.state;
const values = Object.values(checked);
if (values.length === groupKey.length && values.every(v => v)) {
this.setState({ checkAll: true });
} else {
this.setState({ checkAll: false });
}
}
);
};
render() {
console.log(this.state.output);
const { checked, checkAll } = this.state;
return (
<div>
<div className="site-checkbox-all-wrapper">
Select All
<Checkbox
// indeterminate={this.state.indeterminate}
onChange={this.onCheckAllChange}
checked={checkAll}
/>
</div>
{this.state.groupKey.map(item => (
<div className="userpermission-content" key={item.id}>
{item.label}
<Checkbox
onChange={e => this.onChange(e, item.key)}
value={item.key}
checked={checked[item.key]}
/>{" "}
</div>
))}
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("container"));
Related
I want to add new Objects when user click on checkbox. For example , When user click on group , it will store data {permission:{group:["1","2"]}}. If I click on topgroup , it will store new objects with previous one
{permission:{group:["1","2"]},{topGroup:["1","2"]}}.
1st : The problem is that I can not merge new object with previous one . I saw only one objects each time when I click on the group or topgroup.
onChange = value => checked => {
this.setState({ checked }, () => {
this.setState(prevState => {
Object.assign(prevState.permission, { [value]: this.state.checked });
});
});
};
<CheckboxGroup
options={options}
value={checked}
onChange={this.onChange(this.props.label)}
/>
Here is my codesanbox:https://codesandbox.io/s/stackoverflow-a-60764570-3982562-v1-0qh67
It is a lot of code because I've added set and get to set and get state. Now you can store the path to the state in permissionsKey and topGroupKey. You can put get and set in a separate lib.js.
In this example Row is pretty much stateless and App holds it's state, this way App can do something with the values once the user is finished checking/unchecking what it needs.
const Checkbox = antd.Checkbox;
const CheckboxGroup = Checkbox.Group;
class Row extends React.Component {
isAllChecked = () => {
const { options, checked } = this.props;
return checked.length === options.length;
};
isIndeterminate = () => {
const { options, checked } = this.props;
return (
checked.length > 0 && checked.length < options.length
);
};
render() {
const {
options,
checked,
onChange,
onToggleAll,
stateKey,
label,
} = this.props; //all data and behaviour is passed by App
return (
<div>
<div className="site-checkbox-all-wrapper">
<Checkbox
indeterminate={this.isIndeterminate()}
onChange={e =>
onToggleAll(e.target.checked, stateKey)
}
checked={this.isAllChecked()}
>
Check all {label}
</Checkbox>
<CheckboxGroup
options={options}
value={checked}
onChange={val => {
onChange(stateKey, val);
}}
/>
</div>
</div>
);
}
}
//helper from https://gist.github.com/amsterdamharu/659bb39912096e74ba1c8c676948d5d9
const REMOVE = () => REMOVE;
const get = (object, path, defaultValue) => {
const recur = (current, path) => {
if (current === undefined) {
return defaultValue;
}
if (path.length === 0) {
return current;
}
return recur(current[path[0]], path.slice(1));
};
return recur(object, path);
};
const set = (object, path, callback) => {
const setKey = (current, key, value) => {
if (Array.isArray(current)) {
return value === REMOVE
? current.filter((_, i) => key !== i)
: current.map((c, i) => (i === key ? value : c));
}
return value === REMOVE
? Object.entries(current).reduce((result, [k, v]) => {
if (k !== key) {
result[k] = v;
}
return result;
}, {})
: { ...current, [key]: value };
};
const recur = (current, path) => {
if (path.length === 1) {
return setKey(
current,
path[0],
callback(current[path[0]])
);
}
return setKey(
current,
path[0],
recur(current[path[0]], path.slice(1))
);
};
return recur(object, path, callback);
};
class App extends React.Component {
state = {
permission: { group: [] },
topGroup: [],
some: { other: [{ nested: { state: [] } }] },
};
permissionsKey = ['permission', 'group']; //where to find permissions in state
topGroupKey = ['topGroup']; //where to find top group in state
someKey = ['some', 'other', 0, 'nested', 'state']; //where other group is in state
onChange = (key, value) => {
//use set helper to set state
this.setState(set(this.state, key, arr => value));
};
isIndeterminate = () =>
!this.isEverythingChecked() &&
[
this.permissionsKey,
this.topGroupKey,
this.someKey,
].reduce(
(result, key) =>
result || get(this.state, key).length,
false
);
toggleEveryting = e => {
const checked = e.target.checked;
this.setState(
[
this.permissionsKey,
this.topGroupKey,
this.someKey,
].reduce(
(result, key) =>
set(result, key, () =>
checked
? this.plainOptions.map(({ value }) => value)
: []
),
this.state
)
);
};
onToggleAll = (checked, key) => {
this.setState(
//use set helper to set state
set(this.state, key, () =>
checked
? this.plainOptions.map(({ value }) => value)
: []
)
);
};
isEverythingChecked = () =>
[
this.permissionsKey,
this.topGroupKey,
this.someKey,
].reduce(
(result, key) =>
result &&
get(this.state, key).length ===
this.plainOptions.length,
true
);
plainOptions = [
{ value: 1, name: 'Apple' },
{ value: 2, name: 'Pear' },
{ value: 3, name: 'Orange' },
];
render() {
return (
<React.Fragment>
<h1>App state</h1>
{JSON.stringify(this.state)}
<div>
<Checkbox
indeterminate={this.isIndeterminate()}
onChange={this.toggleEveryting}
checked={this.isEverythingChecked()}
>
Toggle everything
</Checkbox>
</div>
{[
{ label: 'group', stateKey: this.permissionsKey },
{ label: 'top', stateKey: this.topGroupKey },
{ label: 'other', stateKey: this.someKey },
].map(({ label, stateKey }) => (
<Row
key={label}
options={this.plainOptions}
// use getter to get state selected value
// for this particular group
checked={get(this.state, stateKey)}
label={label}
onChange={this.onChange} //change behaviour from App
onToggleAll={this.onToggleAll} //toggle all from App
//state key to indicate what state needs to change
// used in setState in App and passed to set helper
stateKey={stateKey}
/>
))}
</React.Fragment>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
<link href="https://cdnjs.cloudflare.com/ajax/libs/antd/4.0.3/antd.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/antd/4.0.3/antd.js"></script>
<div id="root"></div>
I rewrite all the handlers.
The bug in your code is located on the usage of antd Checkbox.Group component with map as a child component, perhaps we need some key to distinguish each of the Row. Simply put them in one component works without that strange state update.
As the demand during communication, the total button is also added.
And, we don't need many states, keep the single-source data is always the best practice.
import React from "react";
import ReactDOM from "react-dom";
import "antd/dist/antd.css";
import "./index.css";
import { Checkbox } from "antd";
const group = ["group", "top"];
const groupItems = ["Apple", "Pear", "Orange"];
const CheckboxGroup = Checkbox.Group;
class App extends React.Component {
constructor() {
super();
this.state = {
permission: {}
};
}
UNSAFE_componentWillMount() {
this.setDefault(false);
}
setDefault = fill => {
const temp = {};
group.forEach(x => (temp[x] = fill ? groupItems : []));
this.setState({ permission: temp });
};
checkLength = () => {
const { permission } = this.state;
let sum = 0;
Object.keys(permission).forEach(x => (sum += permission[x].length));
return sum;
};
/**
* For total
*/
isTotalIndeterminate = () => {
const len = this.checkLength();
return len > 0 && len < groupItems.length * group.length;
};
onCheckTotalChange = () => e => {
this.setDefault(e.target.checked);
};
isTotalChecked = () => {
return this.checkLength() === groupItems.length * group.length;
};
/**
* For each group
*/
isIndeterminate = label => {
const { permission } = this.state;
return (
permission[label].length > 0 &&
permission[label].length < groupItems.length
);
};
onCheckAllChange = label => e => {
const { permission } = this.state;
const list = e.target.checked ? groupItems : [];
this.setState({ permission: { ...permission, [label]: list } });
};
isAllChecked = label => {
const { permission } = this.state;
return !groupItems.some(x => !permission[label].includes(x));
};
/**
* For each item
*/
isChecked = label => {
const { permission } = this.state;
return permission[label];
};
onChange = label => e => {
const { permission } = this.state;
this.setState({ permission: { ...permission, [label]: e } });
};
render() {
const { permission } = this.state;
console.log(permission);
return (
<React.Fragment>
<Checkbox
indeterminate={this.isTotalIndeterminate()}
onChange={this.onCheckTotalChange()}
checked={this.isTotalChecked()}
>
Check all
</Checkbox>
{group.map(label => (
<div key={label}>
<div className="site-checkbox-all-wrapper">
<Checkbox
indeterminate={this.isIndeterminate(label)}
onChange={this.onCheckAllChange(label)}
checked={this.isAllChecked(label)}
>
Check all
</Checkbox>
<CheckboxGroup
options={groupItems}
value={this.isChecked(label)}
onChange={this.onChange(label)}
/>
</div>
</div>
))}
</React.Fragment>
);
}
}
ReactDOM.render(<App />, document.getElementById("container"));
Try it online:
Please try this,
onChange = value => checked => {
this.setState({ checked }, () => {
this.setState(prevState => {
permission : { ...prevSatate.permission , { [value]: this.state.checked }}
});
});
};
by using spread operator you can stop mutating the object. same way you can also use object.assign like this.
this.setState(prevState => {
permission : Object.assign({} , prevState.permission, { [value]: this.state.checked });
});
And also i would suggest not to call setState in a callback. If you want to access the current state you can simply use the current checked value which you are getting in the function itself.
so your function becomes ,
onChange = value => checked => {
this.setState({ checked });
this.setState(prevState => {return { permission : { ...prevSatate.permission, { [value]: checked }}
}});
};
Try the following
//Inside constructor do the following
this.state = {checkState:[]}
this.setChecked = this.setChecked.bind(this);
//this.setChecked2 = this.setChecked2.bind(this);
//Outside constructor but before render()
setChecked(e){
this.setState({
checkState : this.state.checkState.concat([{checked: e.target.id + '=>' + e.target.value}])
//Id is the id property for a specific(target) field
});
}
//Finally attack the method above.i.e. this.setChecked to a form input.
Hope it will address your issues
I am making API calls and rendering different components within an object. One of those is illustrated below:
class Bases extends Component {
constructor() {
super();
this.state = {
'basesObject': {}
}
}
componentDidMount() {
this.getBases();
}
getBases() {
fetch('http://localhost:4000/cupcakes/bases')
.then(results => results.json())
.then(results => this.setState({'basesObject': results}))
}
render() {
let {basesObject} = this.state;
let {bases} = basesObject;
console.log(bases);
//FALSY values: undefined, null, NaN, 0, false, ""
return (
<div>
{bases && bases.map(item =>
<button key={item.key} className="boxes">
{/* <p>{item.key}</p> */}
<p>{item.name}</p>
<p>${item.price}.00</p>
{/* <p>{item.ingredients}</p> */}
</button>
)}
</div>
)
}
}
The above renders a set of buttons. All my components look basically the same.
I render my components here:
class App extends Component {
state = {
ordersArray: []
}
render() {
return (
<div>
<h1>Bases</h1>
<Bases />
<h1>Frostings</h1>
<Frostings />
<h1>Toppings</h1>
<Toppings />
</div>
);
}
}
I need to figure out the simplest way to, when a button is clicked by the user, add the key of each clicked element to a new array and I am not sure where to start. The user must select one of each, but is allowed to select as many toppings as they want.
Try this
We can use the same component for all categories. All the data is handled by the parent (stateless component).
function Buttons({ list, handleClick }) {
return (
<div>
{list.map(({ key, name, price, isSelected }) => (
<button
className={isSelected ? "active" : ""}
key={key}
onClick={() => handleClick(key)}
>
<span>{name}</span>
<span>${price}</span>
</button>
))}
</div>
);
}
Fetch data in App component, pass the data and handleClick method into Buttons.
class App extends Component {
state = {
basesArray: [],
toppingsArray: []
};
componentDidMount() {
// Get bases and toppings list, and add isSelected attribute with default value false
this.setState({
basesArray: [
{ key: "bases1", name: "bases1", price: 1, isSelected: false },
{ key: "bases2", name: "bases2", price: 2, isSelected: false },
{ key: "bases3", name: "bases3", price: 3, isSelected: false }
],
toppingsArray: [
{ key: "topping1", name: "topping1", price: 1, isSelected: false },
{ key: "topping2", name: "topping2", price: 2, isSelected: false },
{ key: "topping3", name: "topping3", price: 3, isSelected: false }
]
});
}
// for single selected category
handleSingleSelected = type => key => {
this.setState(state => ({
[type]: state[type].map(item => ({
...item,
isSelected: item.key === key
}))
}));
};
// for multiple selected category
handleMultiSelected = type => key => {
this.setState(state => ({
[type]: state[type].map(item => {
if (item.key === key) {
return {
...item,
isSelected: !item.isSelected
};
}
return item;
})
}));
};
// get final selected item
handleSubmit = () => {
const { basesArray, toppingsArray } = this.state;
const selectedBases = basesArray.filter(({ isSelected }) => isSelected);
const selectedToppings = toppingsArray.filter(({ isSelected }) => isSelected);
// submit the result here
}
render() {
const { basesArray, toppingsArray } = this.state;
return (
<div>
<h1>Bases</h1>
<Buttons
list={basesArray}
handleClick={this.handleSingleSelected("basesArray")}
/>
<h1>Toppings</h1>
<Buttons
list={toppingsArray}
handleClick={this.handleMultiSelected("toppingsArray")}
/>
</div>
);
}
}
export default App;
CSS
button {
margin: 5px;
}
button.active {
background: lightblue;
}
I think the following example would be a good start for your case.
Define a handleClick function where you can set state with setState as the following:
handleClick(item) {
this.setState(prevState => {
return {
...prevState,
clickedItems: [...prevState.clickedItems, item.key]
};
});
}
Create an array called clickedItems in constructor for state and bind handleClick:
constructor() {
super();
this.state = {
basesObject: {},
clickedItems: [],
}
this.handleClick = this.handleClick.bind(this);
}
You need to add a onClick={() => handleClick(item)} handler for onClick:
<button key={item.key} className="boxes" onClick={() => handleClick(item)}>
{/* <p>{item.key}</p> */}
<p>{item.name}</p>
<p>${item.price}.00</p>
{/* <p>{item.ingredients}</p> */}
</button>
I hope that helps!
Iterates on the todos array. Objects inside have the isChecked property. If isChecked === true marks the checkbox, ifisChecked === false the checkbox is uncheckbox. When I click on the checkbox. I can't mark or uncheckbox
Demo here: https://stackblitz.com/edit/react-ds9rsd
class App extends Component {
constructor() {
super();
this.state = {
todos: [
{
name:'A',
id: 1,
isChecked: true
},
{
name:'B',
id: 2,
isChecked: false
},
{
name:'C',
id: 3,
isChecked: true
}
]
};
}
checked = (e) => {
console.log(e.target.checked)
}
render() {
return (
<div>
{this.state.todos.map((todo, index) => {
return <input type="checkbox" checked={todo.isChecked} onChange={(e) => this.checked(e)}/>
})}
</div>
);
}
}
In checked() function you are just logging the value. Instead of that you need to do setState() to save new state.
A possibile solution could be updating the render function like this:
render() {
return (
<div>
{this.state.todos.map((todo, index) => {
return <input label={todo.name} type="checkbox" checked={todo.isChecked}
onChange={(e) => this.checked(todo)}/>
})}
</div>
);
}
and the checked method like this:
checked = (e) => {
this.setState(state => {
const list = state.todos.map((item) => {
if (item.name === e.name) {
return item.isChecked = !item.isChecked;
} else {
return item;
}
});
return {
list,
};
});
}
You will need to add a function and call it for each checkbox
import React, { Component } from "react";
import { render } from "react-dom";
import Hello from "./Hello";
import "./style.css";
class App extends Component {
constructor() {
super();
this.state = {
todos: [
{
name: "A",
id: 1,
isChecked: true
},
{
name: "B",
id: 2,
isChecked: false
},
{
name: "C",
id: 3,
isChecked: true
}
]
};
}
checked = index => {
/** get the current state */
let _todos = this.state.todos;
/** assign opposite value: true to false or false to true */
_todos[index].isChecked = !_todos[index].isChecked;
/** update state */
this.setState({ todos: _todos });
};
render() {
return (
<div>
{this.state.todos.map((todo, index) => {
/** call the function passing the index value */
return (
<input
label={todo.name}
type="checkbox"
checked={todo.isChecked}
onChange={this.checked.bind(this, index)}
/>
);
})}
</div>
);
}
}
render(<App />, document.getElementById("root"));
I've created this cool sandbox that utilises react-select and the creatable feature. It allows you to select from a prepopulated dropdown and at the same time create a custom option by typing into the select field. Once you have typed into the field your option becomes available in the select list.
I have added in options to be grouped - pre-existing fields are fine, but new options I would like to be grouped by a default value e.g. "new group".
Any help would be appreciated.
https://codesandbox.io/s/p5x7m478rm
import React from "react";
import { Field, reduxForm, FieldArray } from "redux-form";
import TextField from "material-ui/TextField";
import { RadioButton, RadioButtonGroup } from "material-ui/RadioButton";
import Checkbox from "material-ui/Checkbox";
import SelectField from "material-ui/SelectField";
import MenuItem from "material-ui/MenuItem";
import asyncValidate from "./asyncValidate";
import validate from "./validate";
import CreatableSelect from "react-select/lib/Creatable";
const CustomStyle = {
option: (base, state) => ({
...base,
display: "inline",
marginRight: "10px",
backgroundColor: state.isSelected ? "#00285C" : "#eee",
cursor: "pointer"
}),
menuList: () => ({
padding: 10,
display: "inline-flex"
}),
menu: () => ({
position: "relative"
})
};
const createOption = (label: string) => ({
label,
value: label.toLowerCase().replace(/\W/g, "")
});
const formatGroupLabel = data => (
<div>
<span>{data.label}</span>
</div>
);
class LastNameSelectInput extends React.Component {
constructor(props) {
super(props);
}
state = {
value: this.props.options[0].options,
options: this.props.options
};
handleCreate = input => (inputValue: any) => {
this.setState({ isLoading: true });
setTimeout(() => {
const { options, value } = this.state;
const newOption = createOption(inputValue);
this.setState({
isLoading: false,
options: [...options, newOption],
value: newOption,
formatGroupLabel: "new label"
});
input.onChange(newOption);
}, 1000);
};
isValidNewOption = (inputValue, selectValue, selectOptions) => {
if (
inputValue.trim().length === 0 ||
selectOptions.find(option => option.name === inputValue)
) {
return false;
}
return true;
};
render() {
const { input, options } = this.props;
return (
<div>
<style>
{`.react-select__dropdown-indicator,.react-select__indicator-separator {
display: none;
}`}
</style>
<CreatableSelect
classNamePrefix="react-select"
options={this.state.options}
menuIsOpen={true}
onChange={value => {
let newValue = input.onChange(value);
this.setState({ value: newValue });
}}
onBlur={() => input.onBlur(input.value)}
onCreateOption={this.handleCreate(input)}
value={this.state.value}
styles={CustomStyle}
isClearable
isValidNewOption={this.isValidNewOption}
formatGroupLabel={formatGroupLabel}
/>
</div>
);
}
}
const MaterialUiForm = props => {
const { handleSubmit, options, pristine, reset, submitting } = props;
return (
<form onSubmit={handleSubmit}>
<div>
<Field name="option" component={LastNameSelectInput} {...props} />
</div>
</form>
);
};
export default reduxForm({
form: "MaterialUiForm", // a unique identifier for this form
validate,
asyncValidate
})(MaterialUiForm);
To achieve your goal I have changed the function handleCreate you have provided and the options props. You can see a live example here.
In MaterialUiForm.js
handleCreate = input => (inputValue: any) => {
this.setState({ isLoading: true });
setTimeout(() => {
const { options } = this.state;
const newOption = createOption(inputValue);
options.map(option => {
if (option.label === "New group") {
return {
label: option.label,
options: option.options.push(newOption)
};
}
return option;
});
this.setState({
isLoading: false,
options: [...options],
value: newOption,
formatGroupLabel: "new label"
});
input.onChange(newOption);
}, 1000);
In index.js
<MaterialUiForm
onSubmit={showResults}
initialValues={{
option: colourOptions,
option: flavourOptions
}}
options={[
{
label: "New group",
options: []
},
{
label: "Colours",
options: colourOptions
},
{
label: "Flavours",
options: flavourOptions
}
]}
/>
There is different and probably smarter way to do it but the logic is the good one.
Print selected radio button value in console.
If all radiogroup is answered then print in console only the selected= true radioValue. for example: if NO radiobutton= true its value is 2. It should print value 2. Like that all true radiovalue should print in console.
Thanks
//array of cards coming from the backend
const data = [
{
cardName: 'Do you want sugar in your coffee',
options: [
{ radioName: 'Yes',radioValue: '1', selected: false },
{ radioName: 'No',radioValue: '2', selected: false }]
},
{
cardName: 'Do you want milk in your coffee',
options: [
{ radioName: 'Yes',radioValue: '1', selected: false },
{ radioName: 'No',radioValue: '2', selected: false }]
},
{
cardName: 'Do you want low-fat-milk in your coffee',
options: [
{ radioName: 'Yes',radioValue: '1', selected: false },
{ radioName: 'No',radioValue: '2', selected: false }]
}
];
class CardsList extends React.Component {
constructor(props) {
super(props);
this.state = {
cards: [],
};
}
componentDidMount() {
setTimeout(() => {
// mimic an async server call
this.setState({ cards: data });
}, 1000);
}
onInputChange = ({ target }) => {
const { cards } = this.state;
const nexState = cards.map(card => {
if (card.cardName !== target.name) return card;
return {
...card,
options: card.options.map(opt => {
const checked = opt.radioName === target.value;
return {
...opt,
selected: checked
}
})
}
});
this.setState({ cards: nexState })
}
onSubmit = () => {
console.log(this.state.cards.map(({ cardName, options }) => {
const option = options.filter(({ selected }) => selected)[0]
return ` ${option.radioValue}`
}))
};
onReset = () => {
this.setState({cards:[]});
}
render() {
const { cards } = this.state;
return (
<div>
{
cards.length < 1 ? "Loading..." :
<div>
{cards.map((card, idx) => (
<ul>
{card.cardName}
{card.options.radioName}
{
card.options.map((lo, idx) => {
return <input
key={idx}
type="radio"
name={card.cardName}
value={lo.radioName}
checked={!!lo.selected}
onChange={this.onInputChange}
/>
})
}
</ul>
))
}
< button onClick={this.onSubmit}>Submit</button>
< button onClick={this.onReset}>Clear</button>
</div>
}
</div>
);
}
}
ReactDOM.render(<CardsList />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
Change your log in onSubmit to this
console.log(this.state.cards.map(({ cardName, options }) => {
const option = options.filter(({ selected }) => selected)[0]
return `${cardName}: ${option.radioName}`
}))
This way you filter the options to the one, where selected is truthy, and take the first one.
To address your first question, just map over your this.state.cards array before doing the log and check, if there is exactly 1 option, where selected is true. If this is not the case, tell the user in whatever way you want.
Also you can remove your constructor and change it to that:
state = {
cards: [],
}
Because you do not access your props in your constructor
You can go with the answer of #george,
for you to check if either of the radio buttons is clicked for each card, you can run a validation check
let unselectedCards = this.state.cards.filter((card) => {
return !card.options[0].selected && !card.options[1].selected
});
use the unselectedCards variable to highlight the cards.
you can use map options again inside the cards map if you would be having more options.