Checking the checkbox is not working in React - javascript

This is a simple TO-DO app in react in which App.js takes data from TodosData.js and the list is shown with the component TodoItem.js. Now the checkboxes and data can be viewed when rendered. But when I click on the checkbox it doesn't work. I tried console logging handleChange function which seems to work. It seems like there maybe problems inside the handleChange function which I can't figure out.
I am following a freecodecamp tutorial on YT and I also checked it several times but can't find the problem here.
App.js code:
import React from 'react';
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 updatedTodos = prevState.todos.map(todo => {
if (todo.id === id) {
todo.completed = !todo.completed
}
return todo
})
return {
todos: updatedTodos
}
})
}
render() {
const todoItems = this.state.todos.map(itemEach => <TodoItem key = {itemEach.id} itemEach = {itemEach}
handleChange = {this.handleChange} />)
return (
<div className = "todo-list">
{todoItems}
</div>
)
}
}
export default App;
TodoItem.js code:
import React from 'react';
function TodoItem(props) {
return(
<div className = "todo-item">
<input type = "checkbox"
checked={props.itemEach.completed}
onChange={() => props.handleChange(props.itemEach.id)}
/>
<p>{props.itemEach.text}</p>
</div>
)
}
export default TodoItem
TodosData.js code:
const todosData = [
{
id: 1,
text: "Take out the trash",
completed: true
},
{
id: 2,
text: "Grocery shopping",
completed: false
},
{
id: 3,
text: "Clearn gecko tank",
completed: false
},
{
id: 4,
text: "Mow lawn",
completed: true
},
{
id: 5,
text: "Catch up on Arrested Development",
completed: false
}
]
export default todosData
Is there a better way to implement this? This way of doing checkbox seems quite complicated for a beginner like myself. Also how can I improve this code?

You are mutating the state in your handleChange function. Hence it gets the prevState twice. Once for the original previous state, next for the update that you make inside the handleChange.
You can probably lose the reference, by spreading the todo from state like this.
this.setState(prevState => {
const updatedTodos = prevState.todos.map(todo => {
const resTodo = { ...todo };
if (todo.id === id) {
resTodo.completed = !resTodo.completed;
}
return resTodo;
});
return {
todos: updatedTodos
};
});
Here's a working codesandbox

Related

Checkbox doesn't change its value on click in React todo application

Please help me with this I don't understand exactly where I doing wrong. So when I click on the checkbox values are not changing(if it's by default true when I click on the click it should the false). For that in onChange in todoList component I am calling handleClick function there I change the todo.completed value(basically toggling the values).
In App.js inside the handleClick method When do console.log(todo) before returning from the map function value is toggling fine, but it is not updated in the updatedTodo.
App.js
import TodosData from "./todoData";
import TodoList from "./todoList";
import "./styles.css";
class App extends Component {
constructor() {
super();
this.state = {
todos: TodosData
}
this.handleChange = this.handleChange.bind(this)
}
handleChange(id) {
this.setState(prevState => {
const updatedTodo = prevState.todos.map(todo => {
// console.log(updatedTodo);
if(todo.id === id) {
console.log("before the opt "+todo.completed);
todo.completed = !todo.completed
console.log("after the opt "+todo.completed);
}
//console.log(todo);
return todo;
})
console.log(updatedTodo);
return {
todos: updatedTodo
}
});
}
render() {
const todoDataComponents = this.state.todos.map(item => {
return <TodoList key = {item.id} item = {item} handleChange = {this.handleChange} />
})
return (
<div className="todo-list">{todoDataComponents}</div>
);
}
}
export default App;
todoList.jsx
class TodoList extends Component {
constructor(props) {
super(props);
this.state = {}
}
render() {
// console.log(this.props)
return (
<div className="todo-item">
<input
type="checkbox"
checked={this.props.item.completed}
onChange = {() => this.props.handleChange(this.props.item.id)}
/>
<p>{this.props.item.text}</p>
</div>
);
}
}
export default TodoList;
todoData.js
const TodosData = [
{ id: 1, text: "Coding", completed: true },
{ id: 2, text: "Exercise", completed: false },
{ id: 3, text: "Learning", completed: true },
{ id: 4, text: "Programming", completed: true },
{ id: 5, text: "inspire", completed: false },
{ id: 6, text: "motivation", completed: true }
];
export default TodosData;
I can't see any error in your handleChange() method, it should work fine. However, I've just updated some of your code which you can test below.
I changed the name of your TodoList as it's not really a list but an item. I also changed it to a functional component as it's only presentational, there is no need to have its own state. Instead of adding a p tag after the input, you should use a label to make it accessible.
I haven't really changed anything inside your handleChange() method, only removed the console.logs and it works as expected.
Update: You're using React.StrictMode, where React renders everything twice on dev. As your handleChange() runs twice, it sets the clicked todo's completed state twice, making it to set back to its original state. So if it's false on first render it sets to true on click, but it's rendered again and on the second one it's back to false. You won't notice it as it's pretty fast.
To avoid it, you need to avoid mutating anything. So I've updated your onChange handler, it returns a new object if the completed property is changed.
Feel free to run the code snippet below and click on the checkboxes or the item texts.
const TodosData = [
{ id: 1, text: 'Coding', completed: true },
{ id: 2, text: 'Exercise', completed: false },
{ id: 3, text: 'Learning', completed: true },
{ id: 4, text: 'Programming', completed: true },
{ id: 5, text: 'Inspire', completed: false },
{ id: 6, text: 'Motivation', completed: true },
];
function TodoItem(props) {
const { handleChange, item } = props;
return (
<div className="todo-item">
<input
type="checkbox"
id={`item-${item.id}`}
checked={item.completed}
onChange={() => handleChange(item.id)}
/>
<label htmlFor={`item-${item.id}`}>{item.text}</label>
</div>
);
}
class App extends React.Component {
constructor() {
super();
this.state = {
todos: TodosData,
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(id) {
this.setState((prevState) => {
const updatedTodo = prevState.todos.map((todo) => {
return todo.id === id ? {
...todo,
completed: !todo.completed,
} : todo;
});
return {
todos: updatedTodo,
};
});
}
render() {
const { todos } = this.state;
return (
<div className="todo-list">
{todos.map((item) => (
<TodoItem
key={item.id}
item={item}
handleChange={this.handleChange}
/>
))}
</div>
);
}
}
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
body {
font-family: sans-serif;
line-height: 1.6;
}
<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>
Instead of using an additional variable, you can do it in one line. Can u see if this works for you.
handleChange(id) {
this.setState(prevState => prevState.map(todo => todo.id === id ? {...todo, completed: !todo.completed} : todo )
}

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] })
}

is there any solution for my react state problem?

So I am a beginner in react. I was messing around with a to-do list and encountered a state related problem.
//main todolist
import React from 'react'
import './todolist.css'
import Itemtodo from './Itemtodo'
class Todolist extends React.Component{
constructor(){
super()
this.state = {
todos: [
{
id:1,
text: 'html',
completed: true
},
{
id:2,
text: 'css',
completed: true
},
{
id:3,
text: 'js',
completed: false
},
{
id:4,
text: 'react',
completed: false
},
{
id:5,
text: 'review',
completed: false
}
]
}
}
// i think it came from this method
ev = (id) =>{
this.setState((prev) =>{
const newarr = prev.todos.map((data) =>{
if(data.id === id){
data.completed = !data.completed
}
return data
})
return {
todos:newarr
}
})
}
render(){
const array = this.state.todos.map(data => <Itemtodo key={data.id} todo={data.text} ic={data.completed} ev={this.ev} id={data.id}/>)
return (
<div className="todolist">
{array}
</div>
)
}
}
export default Todolist
// item todo
import React, { Component } from 'react'
import './itemtodo.css'
export class Itemtodo extends Component {
render() {
return (
<div className ='itemtodo'>
<input type='checkbox' checked={this.props.ic} onChange={() => this.props.ev(this.props.id)}/>
<p>{this.props.todo}</p>
</div>
)
}
}
export default Itemtodo
I REALLY think that the problem was from "main todolist" because if I changed the "ev" method to do "checked every checkbox with a click" like this, it worked
// i think it came from this method
ev = (id) =>{
this.setState((prev) =>{
const newarr = prev.todos.map((data) =>{
data.completed = true
return data
})
return {
todos:newarr
}
}) //set state ends
}
I did some experiment by console loging the "newar" and it did not change. So I think it's because of the
data.completed = !data.completed
did not work please help me! Thank you
Hi Please check this example. It is working fine.
Todolist Component
import React, {Component} from 'react'
class Todolist extends React.Component {
constructor(props) {
super(props);
this.state = {
todos: [
{id: 1, text: 'html', completed: true},
{id: 2, text: 'css', completed: true},
{id: 3, text: 'js', completed: false},
{id: 4, text: 'react', completed: false},
{id: 5, text: 'review', completed: false}
]
}
}
// i think it came from this method
ev = (id, changedValue) => {
this.setState((prev) => {
let item = prev.todos.filter((data) => data.id === id)[0];
item.completed = changedValue;
return{
todos: prev.todos
};
})
};
render() {
const array = this.state.todos.map(data => <Itemtodo key={data.id} todo={data.text} ic={data.completed}
ev={this.ev} id={data.id}/>);
return (
<div className="todolist">
{array}
</div>
)
}
}
export default Todolist
Itemtodo Component
class Itemtodo extends Component {
render() {
return (
<div className='itemtodo'>
<input type='checkbox' checked={this.props.ic} onChange={() => this.props.ev(this.props.id, !this.props.ic)}/>
{this.props.todo}
</div>
)
}
}
Here is a working example of a todo list that has TodoItem as a pure component, because toggleCompleted will not mutate this will work (you have to click on the todo to toggle completed).
//using React.memo will make TodoItem a pure
// component and won't re render if props didn't change
const TodoItem = React.memo(function TodoItem({
todo: { id, name, completed },
toggleCompleted,
}) {
const r = React.useRef(0);
r.current++;
return (
<li
onClick={toggleCompleted(id)}
style={{ cursor: 'pointer' }}
>
rendered:{r.current}, {name}, completed:
{completed.toString()}
</li>
);
});
function TodoList({ todos, toggleCompleted }) {
return (
<ul>
{todos.map((todo) => (
<TodoItem
key={todo.id}
todo={todo}
toggleCompleted={toggleCompleted}
/>
))}
</ul>
);
}
function App() {
const [todos, setTodos] = React.useState([
{ id: 1, name: '1', completed: false },
{ id: 2, name: '2', completed: false },
]);
//use callback so the handler never changes
const toggleCompleted = React.useCallback(
(id) => () =>
setTodos((todos) =>
//map todos into a new array of todos
todos.map(
(todo) =>
todo.id === id
? //shallow copy todo and toggle completed
{ ...todo, completed: !todo.completed }
: todo //just return the todo
)
),
[]
);
return (
<TodoList
todos={todos}
toggleCompleted={toggleCompleted}
/>
);
}
ReactDOM.render(<App />, document.getElementById('root'));
<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>
<div id="root"></div>
And here is a broken example where I will mutate the todo, even though you changed the data you did not change the reference of the todo item so React won't re render pure TodoItem component.
//using React.memo will make TodoItem a pure
// component and won't re render if props didn't change
const TodoItem = React.memo(function TodoItem({
todo: { id, name, completed },
toggleCompleted,
}) {
const r = React.useRef(0);
r.current++;
return (
<li
onClick={toggleCompleted(id)}
style={{ cursor: 'pointer' }}
>
rendered:{r.current}, {name}, completed:
{completed.toString()}
</li>
);
});
function TodoList({ todos, toggleCompleted }) {
return (
<ul>
{todos.map((todo) => (
<TodoItem
key={todo.id}
todo={todo}
toggleCompleted={toggleCompleted}
/>
))}
</ul>
);
}
function App() {
const [todos, setTodos] = React.useState([
{ id: 1, name: '1', completed: false },
{ id: 2, name: '2', completed: false },
]);
//use callback so the handler never changes
const toggleCompleted = React.useCallback(
(id) => () =>
setTodos((todos) =>
//map todos into a new array of todos
todos.map((todo) => {
if (todo.id === id) {
todo.completed = !todo.completed;
console.log('changed todo:', todo);
}
//it is always returning the same todo that it
// got passed into, only mutates the one that
// needs to be toggled but that todo is still the
// same
return todo;
})
),
[]
);
return (
<TodoList
todos={todos}
toggleCompleted={toggleCompleted}
/>
);
}
ReactDOM.render(<App />, document.getElementById('root'));
<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>
<div id="root"></div>

Very simple state change of specific array item in React

I'm trying to change the state of only one specific array item from the reviews array. How can this be done? This code doesn't seem to work:
this.setState({
reviews[2].current: true
});
Here's the full code:
import React, { Component } from "react";
import { render } from "react-dom";
const reviewsInit = [
{
name: "Peter Lahm",
current: null
},
{
name: "Simon Arnold",
current: null
},
{
name: "Claire Pullen",
current: null
}
];
class App extends Component {
constructor() {
super();
this.state = {
name: "React",
reviews: reviewsInit
};
}
change = () => {
this.setState({
reviews[2].current: true
});
};
render() {
return (
<div>
{console.log(this.state.reviews[2].current)}
<button onClick={this.change}>click me</button>
</div>
);
}
}
render(<App />, document.getElementById("root"));
Demo: https://stackblitz.com/edit/react-tbryf5
As you can probably tell I'm new to react! Thanks for any help here
For some context, React detects state change when reference of the state object changes. It does not track deep changes happening in array or the object.
Solution
We need to make another variable with same data (mostly destructuring). Change the value needed. And assign that to state again.
For Object
this.setState({...oldState, keyToChange: 'newValue'});
For Array
const temp = [...oldState];
temp[index] = 'newValue';
this.setState(temp);
Hope it helps.
It's common for an Array state to copy first then update one of its value
change = () => {
const result = [...this.state.reviews];
result[2].current = true;
this.setState({reviews: result});
};
import React, { Component } from "react";
import { render } from "react-dom";
const reviewsInit = [
{
name: "Peter Lahm",
current: null,
},
{
name: "Simon Arnold",
current: null,
},
{
name: "Claire Pullen",
current: null,
},
];
class App extends Component {
constructor() {
super();
this.state = {
name: "React",
reviews: reviewsInit,
};
}
change = () => {
const prevState = [...this.state.reviews];
prevState[2].current = true;
this.setState({
reviews: prevState,
});
};
render() {
return (
<div>
{console.log(this.state.reviews[2].current)}
<button onClick={this.change}>click me</button>
</div>
);
}
}
render(<App />, document.getElementById("root"));

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