Handling Multiple Checkboxes ReactJS? - javascript

I have rendered a list of checkboxes and i am trying to setup an onChange which tracks the selected input and turns the value to true. But i keep getting an error message and also warning with needing a unique key even though i am passing the index.
This is the CodeSandbox
Please check this complete Code:-
import React from "react";
import "./styles.css";
class App extends React.Component {
state = {
checkboxes: [
{ name: "Check 1", value: false },
{ name: "Check 2", value: false },
{ name: "Check 3", value: false }
]
};
checkboxStateHandler = (event, idx) => {
const { checkbox } = this.state;
checkbox.checkboxes[idx] = event.target.checked;
this.setState({
checkbox
});
};
renderCheckboxes = () => {
return this.state.checkboxes.map((cb, index) => (
<label>
{cb.name}
<input
type="checkbox"
key={index}
checked={cb.value}
onChange={this.checkboxStateHandler}
/>
</label>
));
};
render() {
return <div>{this.renderCheckboxes()}</div>;
}
}
export default App;
Any help will be appreciated. Thank you :)

This will work for you:
import React from "react";
import "./styles.css";
class App extends React.Component {
state = {
checkboxes: [
{ name: "Check 1", value: false },
{ name: "Check 2", value: false },
{ name: "Check 3", value: false }
]
};
checkboxStateHandler = (event, idx) => {
const { checkboxes } = this.state;
checkboxes[idx] = {
...checkboxes[idx],
value: event.target.checked,
}
this.setState({
checkboxes
});
};
renderCheckboxes = () => {
return this.state.checkboxes.map((cb, index) => (
<label>
{cb.name}
<input
type="checkbox"
key={index}
checked={cb.value}
onChange={e => this.checkboxStateHandler(e, index)}
/>
</label>
));
};
render() {
return <div>{this.renderCheckboxes()}</div>;
}
}
export default App;
You must send the event and index to the method in order to change the value, also checkboxStateHandler needs a little changes.

There are few changes needs to be done under map method,
-> Assign key to the immediate parent under map method and in your case it is label
<label key={index}>
....
</label>
-> Then you have to modify the onChange value like,
<input
type="checkbox"
checked={cb.value}
onChange={(e) => this.checkboxStateHandler(e,index)}
/>
Here the event and index needs to passed down as an arguments.
-> Then in checkboxStateHandler function get the parameters and assign the event.target.checked value to checkboxes[idx].value
checkboxStateHandler = (event, idx) => {
const { checkboxes } = this.state;
checkboxes[idx].value = event.target.checked;
this.setState({
checkboxes
});
};
The above code will get rid of all warnings and errors.
Forked codesandbox here..

Related

Why checkboxes dont change from checked to unchecked on click?

My React App doesn't work like it should be. The problem is that the checkboxes dont change at all.
I managed to show the checked boxes (the ones with the property of completed=true) and debugging it seems that it works fine when I click but for some reason the box that needs to be changed automatically re-changes on its own.
Do you have any idea why ?
//APP.JS
import React from "react"
import './App.css';
import Header from "./Header"
import TodoItem from "./todoItem";
import todosData from "./todosData"
class App extends React.Component {
constructor() {
super()
this.state = {
todos: todosData
}
this.handleChange = this.handleChange.bind(this)
}
handleChange(id) {
this.setState((prevState) => {
const newArray = prevState.todos.map((elem) => {
if(elem.id === id) {
elem.completed = !(elem.completed)
}
return elem
})
return {
todos: newArray
}
})
}
render() {
const todosArray = this.state.todos.map(item =>
<TodoItem
key={item.id}
item={item}
handleChange={this.handleChange}
/>)
return (
<div className="App">
<Header />
<div className="container">
{todosArray}
</div>
</div>
)
}
}
export default App;
//TODOITEM.JS
import React from "react"
function TodoItem(props) {
return (
<div className="elem-container">
<input type="checkbox"
checked={props.item.completed}
onChange={() => props.handleChange(props.item.id)}
/>
<span className="span-container">{props.item.text}</span>
</div>
)
}
export default TodoItem
//TODOSDATA.JS
const todosData = [
{
id: 1,
text: "Take out the trash",
completed: true
},
{
id: 2,
text: "Grocery shopping",
completed: false
},
{
id: 3,
text: "Clean gecko tank",
completed: false
},
{
id: 4,
text: "Mow lawn",
completed: true
},
{
id: 5,
text: "Catch up on Arrested Development",
completed: false
}
]
export default todosData
Thank you for the help in advance
you need to change two things and it will work just fine
first:
inside todoItem.js
onChange={(e) => props.handleChange(e,props.item.id)}
second:
inside the parent file
handleChange(event, id) {
this.setState((prevState) => {
const newArray = prevState.todos.map((elem) => {
if(elem.id === id) {
elem.completed = event.target.checked
}
return elem
})
return {
todos: newArray
}
})
}
now everything will work as you expected
have a nice day
I'm no expert as I'm learning React myself but looking at the code handleChange(id) doesn't have an else state in its 'if' statement, have you tried adding?
Adding to #mouheb answer, you can simplify one more step. you don't need to map the all elements to update single item. you can change directly item (if it is mutable).
// todoItem.js
onChange={(e) => props.handleChange(e, props.item) }
// parent file
handleChange(event, prop) {
prop.completed = event.target.
this.setState({ todos: this.state.todos }) or this.setState({ todos: [...this.state.todos] })
}

Checkbox does not want to check or uncheck after clicking

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"));

Prevent child's state from reset after parent component state changes also get the values of all child components:ReactJS+ Typescript

I am a bit new to react and I am stuck in this situation where I am implementing custom dropdown filter for a table in react. I have set of dropdown values for each column and there is a Apply button.
I have maintained a child component for this which takes in drop down values and sends the selected one's back to parent. Then I call a back-end API that gives me filtered data which in-turn sets parents state . The problem here is the checkbox values inside dropdown is lost after I get the data and set the parent state.
Each child components has as a set of checkboxes , an Apply and a clear button. So on click of Apply , I have to send the checked one's to the parent or in general whichever the checked one's without losing the previous content.
I am unable to understand why am I losing the checkbox values?
It would be of great help if someone can help me out with this
Sand box: https://codesandbox.io/s/nervous-elgamal-0zztb
I have added the sandbox link with proper comments. Please have a look. I am a bit new to react.
Help would be really appreciated
Parent
import * as React from "react";
import { render } from "react-dom";
import ReactTable from "react-table";
import "./styles.css";
import "react-table/react-table.css";
import Child from "./Child";
interface IState {
data: {}[];
columns: {}[];
selectedValues: {};
optionsForColumns: {};
}
interface IProps {}
export default class App extends React.Component<IProps, IState> {
// Here I have hardcoded the values, but data and optionsForColumns comes from the backend and it is set inside componentDidMount
constructor(props: any) {
super(props);
this.state = {
data: [
{ firstName: "Jack", status: "Submitted", age: "14" },
{ firstName: "Simon", status: "Pending", age: "15" }
],
selectedValues: {},
columns: [],
optionsForColumns: {
firstName: [{ Jack: "4" }, { Simon: "5" }],
status: [{ Submitted: "5" }, { Pending: "7" }]
}
};
}
// Get the values for checkboxes that will be sent to child
getValuesFromKey = (key: any) => {
let data: any = this.state.optionsForColumns[key];
let result = data.map((value: any) => {
let keys = Object.keys(value);
return {
field: keys[0],
checked: false
};
});
return result;
};
// Get the consolidated values from child and then pass it for server side filtering
handleFilter = (fieldName: any, selectedValue: any, modifiedObj: any) =>
{
this.setState(
{
selectedValues: {
...this.state.selectedValues,
[fieldName]: selectedValue
}
},
() => this.handleColumnFilter(this.state.selectedValues)
);
};
// Function that will make server call based on the checked values from child
handleColumnFilter = (values: any) => {
// server side code for filtering
// After this checkbox content is lost
};
// Function where I configure the columns array for the table . (Also data and column fiter values will be set here, in this case I have hardcoded inside constructor)
componentDidMount() {
let columns = [
{
Header: () => (
<div>
<div>
<Child
key="firstName"
name="firstName"
options={this.getValuesFromKey("firstName")}
handleFilter={this.handleFilter}
/>
</div>
<span>First Name</span>
</div>
),
accessor: "firstName"
},
{
Header: () => (
<div>
<div>
<Child
key="status"
name="status"
options={this.getValuesFromKey("status")}
handleFilter={this.handleFilter}
/>
</div>
<span>Status</span>
</div>
),
accessor: "status",
},
{
Header: "Age",
accessor: "age"
}
];
this.setState({ columns });
}
//Rendering the data table
render() {
const { data, columns } = this.state;
return (
<div>
<ReactTable
data={data}
columns={columns}
/>
</div>
);
}
}
const rootElement = document.getElementById("root");
render(<App />, rootElement);
Child
import * as React from "react";
import { Button, Checkbox, Icon } from "semantic-ui-react";
interface IProps {
options: any;
name: string;
handleFilter(val1: any, val2: any, val3: void): void;
}
interface IState {
showList: boolean;
selected: [];
checkboxOptions: any;
}
export default class Child extends React.Component<IProps, IState> {
constructor(props: any) {
super(props);
this.state = {
selected: [],
showList: false,
checkboxOptions: this.props.options.map((option: any) => option.checked)
};
}
// Checkbox change handler
handleValueChange = (event: React.FormEvent<HTMLInputElement>, data: any) => {
const i = this.props.options.findIndex(
(item: any) => item.field === data.name
);
const optionsArr = this.state.checkboxOptions.map(
(prevState: any, si: any) => (si === i ? !prevState : prevState)
);
this.setState({ checkboxOptions: optionsArr });
};
//Passing the checked values back to parent
passSelectionToParent = (event: any) => {
event.preventDefault();
const result = this.props.options.map((item: any, i: any) =>
Object.assign({}, item, {
checked: this.state.checkboxOptions[i]
})
);
const selected = result
.filter((res: any) => res.checked)
.map((ele: any) => ele.field);
console.log(selected);
this.props.handleFilter(this.props.name, selected, result);
};
//Show/Hide filter
toggleList = () => {
this.setState(prevState => ({ showList: !prevState.showList }));
};
//Rendering the checkboxes based on the local state, but still it gets lost after filtering happens
render() {
let { showList } = this.state;
let visibleFlag: string;
if (showList === true) visibleFlag = "visible";
else visibleFlag = "";
return (
<div>
<div style={{ position: "absolute" }}>
<div
className={"ui scrolling dropdown column-settings " + visibleFlag}
>
<Icon className="filter" onClick={this.toggleList} />
<div className={"menu transition " + visibleFlag}>
<div className="menu-item-holder">
{this.props.options.map((item: any, i: number) => (
<div className="menu-item" key={i}>
<Checkbox
name={item.field}
onChange={this.handleValueChange}
label={item.field}
checked={this.state.checkboxOptions[i]}
/>
</div>
))}
</div>
<div className="menu-btn-holder">
<Button size="small" onClick={this.passSelectionToParent}>
Apply
</Button>
</div>
</div>
</div>
</div>
</div>
);
}
}
This appears to be a case of state being managed in an inconvenient way. Currently, the state is managed at the Child level, but it would be easier to manage at the Parent level. This is known as lifting state up in React.
The gist - the shared state is managed in the Parent component, and it's updated by calling a function passed to the Child component. When Apply is clicked, the selected radio value is passed up to the Parent, which merges the new selection into the shared state.
I have created a minimal example of your code, showing how we can lift state up from the Child to the Parent component. I'm also using a few new-ish features of React, like useState to simplify the Child component.
// Child Component
const Child = ({name, options, updateSelections}) => {
const [selected, setSelected] = React.useState([]);
const handleChange = (event) => {
let updated;
if (event.target.checked) {
updated = [...selected, event.target.value];
} else {
updated = selected.filter(v => v !== event.target.value);
}
setSelected(updated);
}
const passSelectionToParent = (event) => {
event.preventDefault();
updateSelections(name, selected);
}
return (
<form>
{options.map(item => (
<label for={name}>
<input
key={name}
type="checkbox"
name={item}
value={item}
onChange={handleChange}
/>
{item}
</label>
))}
<button onClick={passSelectionToParent}>Apply</button>
</form>
)
}
// Parent Component
class Parent extends React.Component {
constructor(props) {
super(props);
this.fields = ["firstName", "status"],
this.state = {
selected: {}
};
}
getValuesFromKey = (data, key) => {
return data.map(item => item[key]);
}
updateSelections = (name, selection) => {
this.setState({
selected: {...this.state.selected, [name]: selection}
}, () => console.log(this.state.selected));
}
render() {
return (
<div>
{this.fields.map(field => (
<Child
key={field}
name={field}
options={this.getValuesFromKey(this.props.data, field)}
updateSelections={this.updateSelections}
/>
))}
</div>
)
}
}
const data = [
{ firstName: "Jack", status: "Submitted" },
{ firstName: "Simon", status: "Pending" },
{ firstName: "Pete", status: "Approved" },
{ firstName: "Lucas", status: "Rejected" }
];
ReactDOM.render(<Parent data={data}/>, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.7.0-alpha.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.7.0-alpha.2/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Your checkbox values are only lost when you hide/show the table, as the table goes out of
DOM the state of it and it's children are lost. When the table is mounted to DOM, Child
component is mounted again initializing a new state taking checkbox values from
getValuesFromKey method of which returns false by default clearing checkbox ticks.
return {
field: keys[0],
checked: false
};
Stackblitz reproducing the issue.
You have to set checkbox values checking the selectedValues object to see if it was selected.
return {
field: keys[0],
checked: this.state.selectedValues[key] && this.state.selectedValues[key].includes(keys[0]),
};

Search and then modify the result input

Goal: Search must work correctly and then be able to modify the found input. I'm trying to figure out why I can't do both.
Observation: the crazy thing I found is it works if I change the key from key={index} to key={variable.value} which doesn't make any sense.
Can someone tell me what I'm doing wrong, or if there's any way to do this better?
You'll understand better if you look at the codesandbox DEMO
index.js
import React from "react";
import ReactDOM from "react-dom";
import Input from './Input'
const dummyVariablies = [
{
name: 'barrack',
value: 11
},
{
name: 'putin',
value: 22
},
{
name: 'trump',
value: 33
}
]
class App extends React.Component {
state = {
search: ''
}
handleSearch = (e) => {
this.setState({
search: e.target.value
})
}
getFilteredVariables = (variables) => {
const { search } = this.state;
return variables.filter(
variable => variable.name.toString().toLowerCase().includes(search.toString().toLowerCase())
);
}
render() {
const variables = this.getFilteredVariables(dummyVariablies || [])
return (
<div>
Goal: Search must work correctly and then be able to modify the found input
<br /> <br /> <br />
Search: <input onChange={this.handleSearch} />
<br /> <br /> <br />
{variables.map((variable, index) => {
return <Input variable={variable} key={index} />
})
}
</div>
)
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Input.js
import React from 'react';
export default class Input extends React.Component {
state = {
name: '',
value: ''
}
componentDidMount() {
this.setState(
{
value: this.props.variable.value,
name: this.props.variable.name
}
)
}
// static getDerivedStateFromProps(nextProps) {
// return {
// value: nextProps.variable.value,
// name: nextProps.variable.name
// }
// }
handleChange = (e) => {
this.setState({ value: e.target.value });
}
render() {
const { value, name } = this.state;
return (
<div>
{name}
<input type="text"
value={value}
onChange={this.handleChange}
/>
</div>
);
}
}
I forked your sandbox here
As you can see I changed the input:
import React from "react";
export default class Input extends React.Component {
state = {
value: this.props.variable.value
};
handleChange = e => {
this.setState({ value: e.target.value });
};
render() {
return (
<div>
{this.props.variable.name}
<input
type="text"
value={this.state.value}
onChange={this.handleChange}
/>
</div>
);
}
}
The problem with your code is that you're sending props to your Input but you're not handling the updates correctly. You're only doing it once, because componentDidMount runs only once when the component is loaded. I simplified it and I'm just passing the props down. This way the filtration works fine.
Keep in mind that if you want to save the values correctly after you edit the inputs you'll have to insert dummyVariablies inside the state of App and then use lifting state up from your Input component. Good example can be found here.

Don't know how to handle input in my project

I'm working under the to-do list project and have got a problem with adding a comment to my list item. This is what I have right now:
App flow
After adding a list item you should be able to click in this item and a new window with comments will appear. In the comments section, comments should be added with Ctrl+Enter combination.
I've got a problem with adding comments to the list item (they should be added to the "comments" array of a particular list item).
Could you please explain what I'm doing wrong and why my comments aren't adding.
UPDATE: I've updated my code but the following mistake appears when I press Ctr+Enter to add a comment: [Error] (http://joxi.ru/Q2KR1G3U4lZJYm)
I've tried to bind the addItem method but no result. What's wrong with the addItem method?
Here is my main component:
App.js
import React, { Component } from 'react';
import './App.css';
import ListInput from './components/listInput'
import ListItem from './components/listItem'
import SideBar from './components/sideBar'
import CommentsSection from './components/commentsSection'
class App extends Component {
constructor(props){
super(props);
this.state = {
items: [
{
id: 0,
text: 'First item',
commentsCount: 0,
comments: [],
displayComment: false
},
{
id: 1,
text: 'Second item',
commentsCount: 0,
comments: [],
displayComment: false
},
{
id: 2,
text: 'Third item',
commentsCount: 0,
comments: [
'Very first comment',
'Second comment',
],
displayComment: false
},
],
nextId: 3,
activeComment: [],
}
}
// Add new item to the list
addItem = inputText => {
let itemsCopy = this.state.items.slice();
itemsCopy.push({id: this.state.nextId, text: inputText});
this.setState({
items: itemsCopy,
nextId: this.state.nextId + 1
})
}
// Remove the item from the list: check if the clicked button id is match
removeItem = id =>
this.setState({
items: this.state.items.filter((item, index) => item.id !== id)
})
setActiveComment = (id) => this.setState({ activeComment: this.state.items[id] });
addComment = (inputComment, activeCommentId ) => {
// find item with id passed and select its comments array
let commentCopy = this.state.items.find(item => item.id === activeCommentId)['comments']
commentCopy.push({comments: inputComment})
this.setState({
comments: commentCopy
})
}
render() {
return (
<div className='App'>
<SideBar />
<div className='flex-container'>
<div className='list-wrapper'>
<h1>Items</h1>
<ListInput inputText='' addItem={this.addItem}/>
<ul>
{
this.state.items.map((item) => {
return <ListItem item={item} key={item.id} id={item.id} removeItem={this.removeItem} setActiveComment={() => this.setActiveComment(item.id)}/>
})
}
</ul>
</div>
<CommentsSection inputComment='' items={this.state.activeComment}/>
</div>
</div>
);
}
}
export default App;
and my Comments Section component:
commentsSection.js
import React from 'react';
import './commentsSection.css';
import CommentInput from './commentInput'
import CommentsItem from './commentsItem'
export default class CommentsSection extends React.Component {
constructor(props){
super(props);
this.state = {value: this.props.inputComment};
this.handleChange = this.handleChange.bind(this);
this.handleEnter = this.handleEnter.bind(this);
this.addComment = this.addComment.bind(this)
}
handleChange = event => this.setState({value: event.target.value})
handleEnter(event) {
if (event.charCode === 13 && event.ctrlKey) {
this.addComment(this.state.value)
}
}
addComment(comment) {
// Ensure the todo text isn't empty
if (comment.length > 0) {
this.props.addComment(comment, this.props.activeComment);
this.setState({value: ''});
}
}
render() {
return (
<div className='component-section'>
<h1>{this.props.items.text}</h1>
<ul>
{ this.props.items.comments &&
this.props.items.comments.map((comment, index) => <p key={index}>{comment}</p>)
}
</ul>
<CommentsItem />
{/*<CommentInput />*/}
<div className='comment-input'>
<input type='text' value={this.state.value} onChange={this.handleChange} onKeyPress={this.handleEnter}/>
</div>
</div>
)
}
}
Change your CommentSection component addComment method and handleEnter method
addComment(comment) {
// Ensure the todo text isn't empty
if (comment.length > 0) {
// pass another argument to this.props.addComment
// looking at your code pass "this.props.items"
this.props.addComment(comment, this.props.items.id);
this.setState({value: ''});
}
}
handleEnter(event) {
if (event.charCode === 13 && event.ctrlKey) {
// passing component state value property as new comment
this.addComment(this.state.value)
}
}
Change your App Component addComment method
addComment = (inputComment, activeCommentId )=> {
// find item with id passed and select its comments array
let commentCopy = this.state.items.find(item => item.id === activeCommentId)['comments']
// if you want to push comments as object
// but looking at your code this is not you want
// commentCopy.push({comments: inputComment})
// if you want to push comments as string
commentCopy.push( inputComment)
this.setState({
comments: commentCopy
})
}

Categories

Resources