Collect checkbox values as an array React - javascript

I have a checkbox component, I want my user to be able to check multiple items, and then the items to be saved in the state as an array.
If I select a checkbox my handleChange function seems to set my array to undefined, I'm not sure if it's the way I am sending the data or If I've setup my checkbox wrong, I'm quite new to React.
My main component is
export default class MainForm extends Component {
state = {
eventFormats: []
}
handleChange = input => event => {
this.setState({[input]: event.target.value})
console.log(this.state)
}
render() {
const eventFormat = {eventFormats: this.state.eventFormats}
return <EventFormat
nextStep={this.nextStep}
handleChange={this.handleChange}
values={eventFormat}
}
}
}
My event form component
export default class EventFormat extends Component {
state = {
eventFormats: [
{id: 1, value: 1, label: "Virtual", isChecked: false},
{id: 2, value: 2, label: "Hybrid", isChecked: false},
{id: 3, value: 3, label: "Live", isChecked: false},
]
}
saveAndContinue = (e) => {
e.preventDefault()
}
render() {
return (
<Form>
<h1 className="ui centered">Form</h1>
<Form.Field>
{
this.state.eventFormats.map((format) => {
return (<CheckBox handleChange={this.props.handleChange} {...format} />)
})
}
</Form.Field>
<Button onClick={this.saveAndContinue}>Next</Button>
</Form>
)
}
}
And finally my checkbox component
const CheckBox = (props) => {
return (<Checkbox label={props.label} onChange={props.handleChange('eventFormats')}/>)
}
export default CheckBox

The error is in your handleChange function, which sets state to a dictionary while you said you want the checkbox's value to be added to the eventFormats array in the state.
export default class MainForm extends Component {
state = {
eventFormats: []
}
handleChange = input => event => {
if (event.target.checked) {
this.setState({eventFormats: this.state.eventFormats.concat([event.target.value])});
} else {
const index = this.state.indexOf(event.target.value);
if (index === -1) {
console.error("checkbox was unchecked but had not been registered as checked before");
} else {
this.setState({eventFormats: this.state.eventFormats.splice(index, 1);
}
}
console.log(this.state)
}
render() {
const eventFormat = {eventFormats: this.state.eventFormats}
return <EventFormat
nextStep={this.nextStep}
handleChange={this.handleChange}
values={eventFormat}
}
}
}

There are a few things to fix:
this.setState({[input]: event.target.value})
this will always overwrite the array(eventFormats) with event.target.value.
<CheckBox handleChange={this.props.handleChange} {...format} />
in the above line, you're passing all the properties in each format object
const CheckBox = (props) => {
return (<Checkbox label={props.label} onChange={props.handleChange('eventFormats')}/>)
}
but here you're only using label and handleChange.
Here's a React StackBlitz that implements what you're looking for. I used <input type="checkbox" />, you can replace this with the Checkbox component you want. See the console logs to know how the state looks after toggling any of the checkboxes.
Also, added some comments to help you understand the changes.
const Checkbox = ({ id, checked, label, handleChange }) => {
return (
<>
<input
type="checkbox"
id={id}
value={checked}
// passing the id from here to figure out the checkbox to update
onChange={e => handleChange(e, id)}
/>
<label htmlFor={id}>{label}</label>
</>
);
};
export default class App extends React.Component {
state = {
checkboxes: [
{ id: 1, checked: false, label: "a" },
{ id: 2, checked: false, label: "b" },
{ id: 3, checked: false, label: "c" }
]
};
handleChange = inputsType => (event, inputId) => {
const checked = event.target.checked;
// Functional update is recommended as the new state depends on the old state
this.setState(prevState => {
return {
[inputsType]: prevState[inputsType].map(iT => {
// if the ids match update the 'checked' prop
return inputId === iT.id ? { ...iT, checked } : iT;
})
};
});
};
render() {
console.log(this.state.checkboxes);
return (
<div>
{this.state.checkboxes.map(cb => (
<Checkbox
key={cb.id}
handleChange={this.handleChange("checkboxes")}
{...cb}
/>
))}
</div>
);
}
}

Related

update checkbox isChecked stauts in parent

Parent component
class Parent extends Component {
constructor(props) {
super(props);
this.state = {
checkedView:[
{id: 1, value: "A", isChecked: false},
{id: 2, value: "B", isChecked: true},
{id: 3, value: "C", isChecked: true},
{id: 4, value: "D", isChecked: true}
],
}
}
handleCheck=(e)=>{
this.setState({ isChecked: e.target.checked});
}
render(){
return(
<div>
<Selection checkedView={this.state.checkedView} handleCheck={this.handleCheck} />
<Content checkedView={this.state.checkedView} />
</div>
);
}
}
Selection component
class Selection extends Component {
constructor(props) {
super(props)
this.state = {
checkedView: this.props.checkedView
}
}
handleCheck = (event) => {
let checkedView = this.props.checkedView;
checkedView.forEach( item => {
if(item.value === event.target.value){
item.isChecked = event.target.checked
}
})
this.setState({
checkedView: checkedView
})
this.props.handleCheck(event)
}
render() {
return (
<div className="">
<ul className="morefeatures">{
this.props.checkedView.map((selection, index) => {
return (<CheckBox key={index} handleCheck={this.handleCheck} {...selection} />)
})
}
</ul>
</div>
);
}
}
CHECKBOX
export const CheckBox = props => {
return (
<li>
<input key={props.id} onClick={props.handleCheck} type="checkbox" checked={props.isChecked} value={props.value} /> {props.value}
</li>
)
}
I have a parent component that controls the content, and the selection component should be able to update its change to the parent component so other child components then can access the value of the checkbox.
When a (or multiple) checkboxes are checked, Content Component receives signal to display relevant content, something like that
The isChecked not change its status no mater the checkbox is checked or not checked.
So how should I modify the code to make the isChecked really work?????
You are not updating the right state. this.setState({ isChecked: e.target.checked}); adds a new variable isChecked to the state. What you want is find the right entry within checkedView and update that object. I would pass down the id to the checkbox and on onClick, I would call handleChecked which receives not only the event but also the id of the checkbox. In handleChecked you can then find the right checkbox based on the id and update that one accordingly.
In parent:
handleCheck=(id, checked)=>{
// deep copy old state (check out lodash for a nicer deepCopy)
const checkedView = JSON.parse(JSON.stringify(this.state.checkedView));
const checkBox = checkedView.find(view => view.id === id);
checkBox.isChecked = checked;
// update whole object of new state
this.setState(checkedView);
}
In Selection:
!Warning! You were altering props, never update props, that's the job of the parent. You also don't need to put the checkedView in the state in Selection, you receive it as a prob, just pass it down.
handleCheck = (event, id) => {
this.props.handleCheck(id, e.target.checked)
}
class Selection extends Component {
constructor(props) {
super(props)
}
handleCheck = (event, id) => {
this.props.handleCheck(id, e.target.checked)
}
render() {
return (
<div className="">
<ul className="morefeatures">{
this.props.checkedView.map((selection) => {
return (<CheckBox key={selection.id} handleCheck={this.handleCheck} {...selection} />)
})
}
</ul>
</div>
);
}
}
In Checkbox, wrap the handleCheck to pass it both the event and the id to identify the checkbox.
<input onClick={(e) => props.handleCheck(e, props.id)} type="checkbox" checked={props.isChecked} value={props.value} /> {props.value}
If I understand your question I think this is what you're looking for - your architecture and logic are a bit off and you've coded yourself into a corner...- Two components (Parent and Child)
Here is your Parent: (keeps the state and all methods that manipulate it)
export default class Parent extends React.Component {
state = {
checkedView: [
{ id: 1, value: 'A', isChecked: false },
{ id: 2, value: 'B', isChecked: true },
{ id: 3, value: 'C', isChecked: true },
{ id: 4, value: 'D', isChecked: true }
]
};
handleCheck = (id) => {
this.setState({
checkedView: this.state.checkedView.map((item) => {
if (item.id === id) {
return {
...item,
isChecked: !item.isChecked
};
} else {
return item;
}
})
});
};
render() {
return (
<div>
{this.state.checkedView.map((item) => (
<Child key={item.id} item={item} handleCheck={this.handleCheck} />
))}
</div>
);
}
}
Here is your Child:
import React from 'react';
export default function Child({ item, handleCheck }) {
return (
<div onClick={() => handleCheck(item.id)}>
{item.value}
<input type='checkbox' defaultChecked={item.isChecked} />
</div>
);
}
Here is a live demo: https://stackblitz.com/edit/react-ddqwpb?file=src%2FApp.js
I'm guessing you can understand what the code is doing... if not ask...

using checkboxes to change state and sort list

I have a list like this:
<div className="doubleCol">
{this.state.symptoms.map(item => (
<ListItem key={item.ObjectID}>
<input type="checkbox" className="sympSelect" />
{item.name}
</ListItem>
))}
</div>
All the items rendered have checkbox and I want it to filter a different list elsewhere on the page based on which boxes are checked. To do that I need the checkboxes to change the state and pass the new state to a method which is supposed to filter and display only those items on the second list with id's associated to items on the first list.
From what I have read it shouldn't matter for this purpose if the checkboxes are controlled or uncontrolled.
class Home extends React.Component {
state = {
conditions: [],
symptoms: [],
selectedSymptom: []
}
componentDidMount() {
this.getConditionsMethod();
this.getSymptomsMethod();
}
getConditionsMethod = () => {
API.getConditions()
.then(data => {
console.log(data);
data.data.sort((a, b) => a.name.localeCompare(b.name))
this.setState({
conditions: data.data
})
})
.catch(err => console.log(err))
};
filterConditionsMethod = () => {
API.getConditions()
.then(data => {
console.log(data);
data.data.sort((a, b) => a.name.localeCompare(b.name));
this.setState({
selectedSymptom: data.data
})
})
.catch(err => console.log(err))
};
But I am kind of stuck on how to structure the onChange for when the box is checked and how to make that implement the filter.
Here is you solution you can add onChange event for checkbox and filter your records as selectedSymptoms and symptoms. Please check code is
import React, { Component } from "react";
class Home extends Component {
constructor(props) {
super(props);
this.state = {
conditions: [],
symptoms: [
{ ObjectID: 1, name: "xyz" },
{ ObjectID: 2, name: "pqr" }
],
selectedSymptom: [],
checked: ""
};
}
updateCheckBox = (event, item) => {
if (event.target.checked) {
let selectedList = this.state.selectedSymptom;
selectedList.push(item);
this.setState({
...this.state,
checked: this.state.checked == "checked" ? "" : "checked",
selectedSymptom: selectedList
});
} else {
const symptomss = this.state.selectedSymptom.filter(element => {
if (element.ObjectID != data.ObjectID) {
return item;
}
});
this.setState({
...this.state,
checked: "",
selectedSymptom: symptomss
});
}
};
render() {
return (
<div className="doubleCol">
{this.state.symptoms.map(item => (
<ListItem key={item.ObjectID}>
<input
type="checkbox"
className="sympSelect"
onChange={this.updateCheckBox(e, item)}
id="symptoms_id"
defaultChecked={this.state.checked}
/>
{item.name}
</ListItem>
))}
</div>
);
}
}
export default Home;

How to clear input values of dynamic form in react

I have a dynamic form as a functional component which is generated via a class based component. I want to make reset button which clears the input field values and sets the state to null array.
Full code is available here:
https://codesandbox.io/s/beautiful-archimedes-o1ygt
I want to make a reset button, clearing all the input values and initializing the Itemvalues array to null.
Even if I set the values to null, it doesn't clear the input field.
However, the problem I'm facing is that since, it is a dynamic form and a functional component it doesn't have a predefined state for each individual form field making it difficult to set value to null.
Can someone please help, I'm stuck on this from a long time
Here's a codesandbox to show you how to reset the items: https://codesandbox.io/s/romantic-heisenberg-93qi7
I also left a note for you on how to get this to work with your API data, see the comment inside onChangeText()
The problem is that the inputs are not controlled by state as you have deduced. We should create an updated object for each item from your API, giving it a value prop.
index.js
import React from "react";
import ReactDOM from "react-dom";
import Cart from "./Cart";
import "./styles.css";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
Items: [],
itemvalues: [{}]
};
this.onChangeText = this.onChangeText.bind(this);
this.getItems = this.getItems.bind(this);
this.handleReset = this.handleReset.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.findFieldIndex = this.findFieldIndex.bind(this);
this.trimText = this.trimText.bind(this);
}
getItems = () => {
/*if the data is coming from an API, store it in an array then .map() over it.
we can add a value prop to the object like:
so you can do something like:
const newItems = [...apiData].map((item) => {
return {
...item,
value: ""
}
})
this.setState({
Items: newItems
})
*/
this.setState({
Items: [
{
name: "item1",
description: "item1",
group: "groupA",
dtype: "str",
value: ""
},
{
name: "item2",
description: "item2",
group: "groupA",
dtype: "str",
value: ""
},
{
name: "item3",
description: "item3",
group: "groupB",
dtype: "str",
value: ""
},
{
name: "item4",
description: "item4",
group: "groupB",
dtype: "str",
value: ""
}
]
});
};
onChangeText = e => {
const updatedItems = [...this.state.Items].map(item => {
if (item.name === e.target.name) {
return {
...item,
value: e.target.value
};
} else {
return item;
}
});
const updatedItemValues = [...updatedItems].reduce((obj, curr) => {
if (!obj[curr.group]) {
obj[curr.group] = [];
}
obj[curr.group] = [...obj[curr.group], { [curr.name]: curr.value }];
return obj;
}, {});
this.setState({
...this.state,
Items: updatedItems,
itemvalues: updatedItemValues
});
};
findFieldIndex = (array, name) => {
return array.findIndex(item => item[name] !== undefined);
};
trimText(str) {
return str.trim();
}
handleReset = () => {
const resetedItems = [...this.state.Items].map(item => {
return {
...item,
value: ""
};
});
this.setState(
{
...this.state,
Items: resetedItems,
itemvalues: []
},
() => console.log(this.state)
);
};
handleSubmit = () => {
console.log(this.state.itemvalues);
};
render() {
return (
<div>
{
<Cart
Items={this.state.Items}
getItems={this.getItems}
handleSubmit={this.handleSubmit}
handleReset={this.handleReset}
onChangeText={this.onChangeText}
/>
}
</div>
);
}
}
Cart.js
import React, { useEffect } from "react";
import Form from "./Form";
const Cart = props => {
useEffect(() => {
props.getItems(props.Items);
}, []);
return (
<div>
<Form Items={props.Items} onChangeText={props.onChangeText} />
<button onClick={props.handleSubmit}>Submit</button>
<button onClick={props.handleReset}>Reset</button>
</div>
);
};
export default Cart;
The Cart component can remain mostly the same, we do not need to pass in props.items to useEffect() dependency.
Form.js
import React from "react";
const Form = props => {
return (
<div>
{props.Items.map(item => {
return (
<input
name={item.name}
placeholder={item.description}
data-type={item.dtype}
data-group={item.group}
onChange={e => props.onChangeText(e)}
value={item.value}
/>
);
})}
</div>
);
};
export default Form;
Now in Form component, we provide each input a value prop that is connected to the item our upper-most parent component-state.
That's pretty much all you need to reset the values.
See if that works for you:
Working example on CodeSandbox
Since you were already using hooks in part of your code, I've converted your class into a functional component using hooks (my advice: learn hooks and forget about class components).
I've added a value property to your INITIAL_STATE so it will keep the input value for each inputItem.
Full CODE:
index.js
import React, { useState } from "react";
import ReactDOM from "react-dom";
import FormV2 from "./FormV2";
import "./styles.css";
function App() {
const INITIAL_STATE = [
{
name: "item1",
description: "item1",
group: "groupA",
dtype: "str",
value: "" // ADDED VALUE PROPERTY TO KEEP THE INPUT VALUE
},
{
name: "item2",
description: "item2",
group: "groupA",
dtype: "str",
value: ""
},
{
name: "item3",
description: "item3",
group: "groupB",
dtype: "str",
value: ""
},
{
name: "item4",
description: "item4",
group: "groupB",
dtype: "str",
value: ""
}
];
const [inputItems, setInputItems] = useState(INITIAL_STATE);
function handleChange(event, index) {
const newValue = event.target.value;
setInputItems(prevState => {
const aux = Array.from(prevState);
aux[index].value = newValue;
return aux;
});
}
function handleReset() {
console.log("Reseting Form to INITIAL_STATE ...");
setInputItems(INITIAL_STATE);
}
function handleSubmit() {
inputItems.forEach(item =>
console.log(
"I will submit input: " + item.name + ", which value is: " + item.value
)
);
}
return (
<FormV2
handleSubmit={handleSubmit}
handleReset={handleReset}
handleChange={handleChange}
inputItems={inputItems}
/>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
FormV2.js
import React from "react";
function FormV2(props) {
const formInputItems = props.inputItems.map((item, index) => (
<div key={item.name}>
{item.name + ": "}
<input
type="text"
data-type={item.dtype}
data-group={item.group}
placeholder={item.description}
value={item.value}
onChange={event => props.handleChange(event, index)}
/>
</div>
));
return (
<React.Fragment>
<form>{formInputItems}</form>
<button onClick={props.handleSubmit}>Submit</button>
<button onClick={props.handleReset}>Reset</button>
<div>State: {JSON.stringify(props.inputItems)}</div>
</React.Fragment>
);
}
export default FormV2;
In order to control the values of the child components (Items) which I presume are input fields you need to be passing down their values from their parent component. So each of your items will have an item.value which is stored in the parent component's state.
That means that in the parent component you will be able to define a method which clears all of the item values it is storing in its state.
That will probably look something like
resetInputs = () => {
this.setState({
inputFields: this.state.inputFields.map(inputField => {
...inputField,
value: ''
}
})
}
Also you'll need to write what kind of tag you want for your code to work, like input.
So what you'll end up with for the code of the child component you shared is something like:
const Form = (props) => {
return (
<div>
{props.Items.map(item => (
<input
name={item.name}
value={item.value}
placeholder={item.description}
onChange={e => props.onChangeText(e)}
/>
)
)}
</div>
);
}
export default Form
You want to manage the state of unknown number N of items, one way to achieve it is by managing a single object which contains all states, for example, setValuesManager manages N inputs and clicking the button reset its state:
function TextAreaManager() {
const [valuesManager, setValuesManager] = useState([...items]);
return (
<Flexbox>
{valuesManager.map((value, i) => (
<TextBoxItem
key={i}
value={value}
onChange={e => {
valuesManager[i] = e.target.value;
setValuesManager([...valuesManager]);
}}
/>
))}
<PinkButton
onClick={() =>
setValuesManager([...Array(valuesManager.length).fill('')])
}
>
Reset All
</PinkButton>
</Flexbox>
);
}
Demo:

Check all checkboxes, and uncheck if any of them deselected in React without jQuery

I would like to select all checkboxes. But if user deselect any checkbox, i want also uncheck "select all" checkbox.
In given example below, How can achieve this?
Live Demo: https://react-f3tcbc.stackblitz.io
Live Editor: https://stackblitz.com/edit/react-f3tcbc
Some samples I looked refer checked as boolean parameter in items. However my items object comes from ajax response as json and they have no value such as checked.
I want to do this in React way. Not jQuery. Any ideas?
One way of going about it is to add an extra property called e.g. isChecked to the data you get from the network request, and use that to control all the
checkboxes.
Example
const posts = [
{
id: 1,
name: "Text 1"
},
{
id: 2,
name: "Text 2"
},
{
id: 3,
name: "Text 3"
}
];
class App extends React.Component {
state = {
name: "React",
posts: [],
isAllChecked: false
};
componentDidMount() {
setTimeout(() => {
this.setState({
posts: posts.map(post => ({ ...post, isChecked: false }))
});
}, 1000);
}
handleSelect = id => {
this.setState(prevState => {
const posts = prevState.posts.map(post =>
post.id === id ? { ...post, isChecked: !post.isChecked } : post
);
const isAllChecked = posts.every(post => post.isChecked);
return { posts, isAllChecked };
});
};
handleSelectAll = () => {
this.setState(prevState => {
const isAllChecked = !prevState.isAllChecked;
const posts = prevState.posts.map(post => ({
...post,
isChecked: isAllChecked
}));
return { posts, isAllChecked };
});
};
render() {
const { posts, isAllChecked } = this.state;
return (
<div>
{posts.map(fx => (
<TableItem
key={fx.id}
id={fx.id}
name={fx.name}
checked={fx.isChecked}
onChange={() => this.handleSelect(fx.id)}
/>
))}
<div>
<label>
<input
type="checkbox"
checked={isAllChecked}
onChange={this.handleSelectAll}
/>
Select all
</label>
</div>
</div>
);
}
}
class TableItem extends React.Component {
render() {
const { checked, onChange, name } = this.props;
return (
<tr>
<td>
<input type="checkbox" checked={checked} onChange={onChange} />
</td>
<td>{name}</td>
</tr>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>

reactjs -- Solving setState async problem

I've read this post: React setState not Updating Immediately
and realized that setState is async and may require a second arg as a function to deal with the new state.
Now I have a checkbox
class CheckBox extends Component {
constructor() {
super();
this.state = {
isChecked: false,
checkedList: []
};
this.handleChecked = this.handleChecked.bind(this);
}
handleChecked () {
this.setState({isChecked: !this.state.isChecked}, this.props.handler(this.props.txt));
}
render () {
return (
<div>
<input type="checkbox" onChange={this.handleChecked} />
{` ${this.props.txt}`}
</div>
)
}
}
And is being used by another app
class AppList extends Component {
constructor() {
super();
this.state = {
checked: [],
apps: []
};
this.handleChecked = this.handleChecked.bind(this);
this.handleDeleteKey = this.handleDeleteKey.bind(this);
}
handleChecked(client_id) {
if (!this.state.checked.includes(client_id)) {
let new_apps = this.state.apps;
if (new_apps.includes(client_id)) {
new_apps = new_apps.filter(m => {
return (m !== client_id);
});
} else {
new_apps.push(client_id);
}
console.log('new apps', new_apps);
this.setState({apps: new_apps});
// this.setState({checked: [...checked_key, client_id]});
console.log(this.state);
}
}
render () {
const apps = this.props.apps.map((app) =>
<CheckBox key={app.client_id} txt={app.client_id} handler={this.handleChecked}/>
);
return (
<div>
<h4>Client Key List:</h4>
{this.props.apps.length > 0 ? <ul>{apps}</ul> : <p>No Key</p>}
</div>
);
}
}
So every time the checkbox status changes, I update the this.state.apps in AppList
when I console.log new_apps, everything works accordingly, but console.log(this.state) shows that the state is not updated immediately, which is expected. What I need to know is how I can ensure the state is updated when I need to do further actions (like register all these selected strings or something)
setState enables you to make a callback function after you set the state so you can get the real state
this.setState({stateYouWant}, () => console.log(this.state.stateYouWant))
in your case:
this.setState({apps: new_apps}, () => console.log(this.state))
The others have the right answer regarding the setState callback, but I would also suggest making CheckBox stateless and pass isChecked from MyApp as a prop. This way you're only keeping one record of whether the item is checked, and don't need to synchronise between the two.
Actually there shouldn't be two states keeping the same thing. Instead, the checkbox should be stateless, the state should only be kept at the AppList and then passed down:
const CheckBox = ({ text, checked, onChange }) =>
(<span><input type="checkbox" checked={checked} onChange={() => onChange(text)} />{text}</span>);
class AppList extends React.Component {
constructor() {
super();
this.state = {
apps: [
{name: "One", checked: false },
{ name: "Two", checked: false }
],
};
}
onChange(app) {
this.setState(
previous => ({
apps: previous.apps.map(({ name, checked }) => ({ name, checked: checked !== (name === app) })),
}),
() => console.log(this.state)
);
}
render() {
return <div>
{this.state.apps.map(({ name, checked }) => (<CheckBox text={name} checked={checked} onChange={this.onChange.bind(this)} />))}
</div>;
}
}
ReactDOM.render(<AppList />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>

Categories