I am thoroughly confused and apologize ahead of time if this is a stupid question, I did my research and couldn't find the answer. I installed react following the official tutorial here. Then I followed the tutorial to the end with no problem. Everything worked right. I'm using Visual Studio Code in Ubuntu 20.04.02. Visual Studios Code reports: Version: 1.59.0 Commit: 379476f0e13988d90fab105c5c19e7abc8b1dea8 Date: 2021-08-04T23:13:20.182Z Electron: 13.1.7 Chrome: 91.0.4472.124 Node.js: 14.16.0 V8: 9.1.269.36-electron.0 OS: Linux x64 5.11.0-25-generic snap
Then I started following the following course: scrimba and confronted with an exercise I adopted the following solution to it:
handleChange(id) {
this.setState(prevState => {
let newTodos = [...prevState.todos];
const item = newTodos.find(item => item.id==id)
if (newTodos == prevState.todos){
console.log("Alarm, this should be true");
}
item.completed = !item.completed
console.log('newTodos= ')
console.log(newTodos)
console.log('prevState.todos= ')
console.log(prevState.todos)
return {
todos: newTodos
}
})
}
The output I get from the console shows that preState was changed by me changing newTodos is this:
newTodos=
App.js:38 (5) [{…}, {…}, {…}, {…}, {…}]0: {id: 1, text: "Take out the trash", completed: true}1: {id: 2, text: "Grocery shopping", completed: true}2: {id: 3, text: "Clean gecko tank", completed: false}3: {id: 4, text: "Mow lawn", completed: true}4: {id: 5, text: "Catch up on Arrested Development", completed: false}length: 5[[Prototype]]: Array(0)
App.js:39 prevState.todos=
App.js:40 (5) [{…}, {…}, {…}, {…}, {…}]0: {id: 1, text: "Take out the trash", completed: true}1: {id: 2, text: "Grocery shopping", completed: true}2: {id: 3, text: "Clean gecko tank", completed: false}3: {id: 4, text: "Mow lawn", completed: true}4: {id: 5, text: "Catch up on Arrested Development", completed: false}length: 5[[Prototype]]: Array(0)
but now, if I use the second solution of the author of the course (his first solution also changed preState, but it was for a preditable reason) this doesn't happen any more. Here is his solution and the console output for the same change in the checkboxes (I check box corresponding with todos[1].id = 2
)
handleChange(id) {
this.setState(prevState => {
const updatedTodos = prevState.todos.map(todo => {
if (todo.id === id) {
return {
...todo,
completed: !todo.completed
}
}
return todo
})
console.log(prevState.todos)
console.log(updatedTodos)
return {
todos: updatedTodos
}
})
}
and the console output, now is correct with preState not changing
(5) [{…}, {…}, {…}, {…}, {…}]0: {id: 1, text: "Take out the trash", completed: true}1: {id: 2, text: "Grocery shopping", completed: false}2: {id: 3, text: "Clean gecko tank", completed: false}3: {id: 4, text: "Mow lawn", completed: true}4: {id: 5, text: "Catch up on Arrested Development", completed: false}length: 5[[Prototype]]: Array(0)
App.js:59 (5) [{…}, {…}, {…}, {…}, {…}]0: {id: 1, text: "Take out the trash", completed: true}1: {id: 2, text: "Grocery shopping", completed: true}2: {id: 3, text: "Clean gecko tank", completed: false}3: {id: 4, text: "Mow lawn", completed: true}4: {id: 5, text: "Catch up on Arrested Development", completed: false}length: 5[[Prototype]]: Array(0)
Why is prevState changing in my solution? What am I missing?
Thanks for any help, I am afraid of having a major missunderstanding of either javascript or react.
I will include all the files required to reproduce this. index.js and index.css go in the src subdirectory of the react installation and the other files go in the src/components subdirectory.
Index.js file:
import React from 'react'
import ReactDOM from 'react-dom'
import App from './components/App'
import './index.css'
ReactDOM.render(<App />, document.getElementById('root'))
App.js file for the solution that works, the other commented out:
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) {
// My solution, which has the problem of changing the preState, no idea why
// this.setState(prevState => {
// let newTodos = [...prevState.todos];
// const item = newTodos.find(item => item.id==id)
// if (newTodos == prevState.todos){
// console.log("Alarm, this should be true");
// }
// item.completed = !item.completed
// console.log('newTodos= ')
// console.log(newTodos)
// console.log('prevState.todos= ')
// console.log(prevState.todos)
// return {
// todos: newTodos
// }
// })
/***************** HIS SOLUTION***************** */
this.setState(prevState => {
const updatedTodos = prevState.todos.map(todo => {
if (todo.id === id) {
return {
...todo,
completed: !todo.completed
}
}
return todo
})
console.log(prevState.todos)
console.log(updatedTodos)
return {
todos: updatedTodos
}
})
}
render() {
const todoItems = this.state.todos.map(item => <TodoItem handleChange= {this.handleChange} key={item.id} item={item} />)
return (
<div className="todo-list">
{todoItems}
</div>
)
}
}
export default App
The child component TodoItem.js:
function TodoItem(props) {
return (
<div className="todo-item">
<input
type="checkbox"
checked={props.item.completed}
onChange={() => props.handleChange(props.item.id)}
/>
<p>{props.item.text}</p>
</div>
)
}
export default TodoItem
And the data used to run this:
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
I think you might have found the difference between two examples.
[...prevState.todos]
The problem is that items of the todos
are Object like this.
{
id: 1,
text: "Take out the trash",
completed: true
},
Javascript passes object with reference (similar to pointer).
const item = newTodos.find(item => item.id==id)
if (newTodos == prevState.todos){
console.log("Alarm, this should be true");
}
item.completed = !item.completed
As item
is a reference to an object here, chaging item.completed
results in changing the prevState.
Let's see the second example.
As you can see, they made a new copy of todo
in the map
function
const updatedTodos = prevState.todos.map(todo => {
if (todo.id === id) {
return {
...todo,
completed: !todo.completed
}
}
return todo
})
Here ...todo, completed: !todo.completed
means use the field :value pair of todo
and set the completed
field value as !todo.completed