Reputation: 99
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;
Upvotes: 0
Views: 1603
Reputation: 23189
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.log
s 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>
Upvotes: 2
Reputation: 13598
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 )
}
Upvotes: 0