Reputation: 1336
I am learning React.js and I have a very strange issue. I follow the tutorial step by step, when I call this.setState
to update the jokes array in this.state
object with the modified one it doesn't change. I want to toggle the completed
property to true
/ false
on checkbox click. There is no console error.
Any opinion is welcome.
JokesTemplate.js
function JokeTemplate(props){
return (
<div className="col-md-4">
<label>Joke {props.item.id}</label>
<p>Question {props.item.desc}</p>
<p>Answer: {props.item.ans}</p>
<input type="checkbox" checked={props.item.completed} onChange={() => props.handleChange(props.item.id)} />
</div>
);
}
export default JokeTemplate;
Jokes.js
import React from 'react';
import JokeTemplate from './JokeTemplate';
const jokes = [{
id:1,
desc: "Question 1",
ans: "Answer 1",
completed: false
},
{
id:2,
desc: "Question 2",
ans: "Answer 2",
completed: true
},
{
id:3,
desc: "Question 3",
ans: "Answer 3",
completed: false
}];
class Jokes extends React.Component{
constructor(){
super()
this.state = {
jokesLst: jokes
}
this.handleChange = this.handleChange.bind(this);
}
handleChange(id){
this.setState(prevState => {
let updatedObj = prevState.jokesLst.map( item =>{
if(item.id === id){
item.completed = !item.completed;
}
return item;
})
return { jokesLst: updatedObj }
});
}
render(){
const jokesComponentArr = this.state.jokesLst.map( joke => <JokeTemplate key={joke.id} item={joke} handleChange={this.handleChange} />);
return (
<>
{jokesComponentArr}
</>
)
}
}
export default Jokes;
App.js
import React from 'react';
import logo from './logo.svg';
import './App.css';
import NavBar from './components/NavBar';
import Jokes from './components/Jokes';
function App() {
return (
<div className="App">
<NavBar />
<header className="App-header">
<Jokes />
</header>
</div>
);
}
export default App;
Thanks in advance.
Upvotes: 1
Views: 85
Reputation: 1
I think all you need to modify is only at the handleChange() method inside Jokes.js
I have never seen such way of putting logic to filter "item" and modify its "completed" field inside of this.setState() method. But I would suggest you to do it this way. So here we create a new instance of updatedObj, do all the logic for manipulating the completed field. and then finally just call setState and pass the updatedObj to it directly. I tested this on codesandbox, and it works.
handleChange(id) {
let updatedObj = this.state.jokesLst.map((item) => {
if (item.id === id) {
item.completed = !item.completed;
}
return item;
});
this.setState({
jokesLst: updatedObj
});
};
Upvotes: 0
Reputation: 15166
It seems your code still modifies the original elements in the array because they are objects which are not referenced by value. To eliminate the issue you need to copy each elements in your .map()
call in your callback within prevState
.
In handleChange
you can try the following instead as:
this.setState(prevState => {
let updatedObj = prevState.jokesLst.map(item => {
const newItem = { ...item }
if (newItem.id === id) {
newItem.completed = !newItem .completed
}
return newItem
})
return { jokesLst: updatedObj }
})
See the difference with the extra const newItem = { ...item }
line above.
Or you can use it even shorter:
this.setState(prevState => ({
...prevState,
jokesLst: prevState.jokesLst.map(item => ({
...item,
completed: item.id === id ? !item.completed : item.completed
}))
})
Upvotes: 1