I am trying to add multiple values from an array to the state, but for some reasons, only the last one is added into the state
here is the code:
this.state = { languageList: {} };
const languages = ['English', 'Spanish', 'Russian']
languages.map(language =>
this.setState({
languageList: {
...this.state.languageList,
[language]: true,
},
}),
);
Can anyone help?
Expected output:
languageList: { Engish: true, Spanish: true, Russian: true }
You can call setState only once rather than calling it into a loop.
If you do it into a loop, only the last setState will be applied because the previous ones haven't been executed yet.
this.setState({
languageList: {
...this.state.languageList,
...Object.fromEntries(languages.map(language => [language, true]))
}
}),
If you really want to call setState into a loop (I don't recommend it because it cause a lot of state updates), you must use the updater function to do the update from the previous state
languages.map(language =>
this.setState(prevState => ({
languageList: {
...prevState.languageList,
[language]: true,
},
})),
);
Since you're updating state based on previous values, you need to use the updater form:
this.setState(prevState => ({
languageList: {
...prevState.languageList,
[language]: true,
},
})),
This is because setState is async, and your loop will run to completion before the state is actually updated. Meaning this.state.languageList will be the same empty object for each iteration.
Related
constructor(props) {
super(props);
this.state = {
active: false,
showSideBar: false,
className: ""
}
}
componentDidMount() {
if (this.props.overlay) {
this.setState({
className: "wrapper_overlay"
});
alert(this.state.className);
}
else if (this.props.SideBarWithIcon) {
this.setState({
className: "wrapper_clopsed"
});
}
}
I am updating my state with the help of the props but the component is getting props but state is not updating
setState is asynchronous. Just alert in a callback to the method instead.
if (this.props.overlay) {
this.setState(
{ className: "wrapper_overlay" },
() => alert(this.state.className);
);
}
Note: you can, and should, also use shouldComponentUpdate to check for when a setState call completes
Since setstate is async in nature so you maynot see updated state in alert.
You can use that in callback which will be called once the setstate is done. like this
componentDidMount() {
if (this.props.overlay) {
this.setState({
className: "wrapper_overlay"
}, ()=> {alert(this.state.className);});
}
State updates may be asynchronous
React may batch multiple setState() calls into a single update for
performance.
Because this.props and this.state may be updated asynchronously, you
should not rely on their values for calculating the next state.
// Wrong
this.setState({
className: "wrapper_overlay"
});
To fix it, use a second form of setState() that accepts a function
rather than an object.
// Correct
this.setState((state, props) => ({
className: "wrapper_overlay"
}));
or just
this.setState(() => ({
className: "wrapper_overlay"
}));
In React.js, running program thread does not wait for setting state and continues its execution, until and unless operation defined in setState() callback.
Your state is getting set but as your alert() is after setstate i.e. not in callback of setState() that's why its getting previous value because at the time state is setting new value, thread is not waiting but executing the next instruction which is alert().
if (this.props.overlay) {
this.setState({ className: "wrapper_overlay" }, () => alert(this.state.className););
}
Hope this works for you.
Just use the callback method to update the state, as
setState() might work asynchronously.
this.setState(() => {
className: "wrapper_clopsed"
});
This question already has answers here:
Whats the best way to update an object in an array in ReactJS?
(4 answers)
Closed 4 years ago.
I am using React components which look like this (a simplified version of the components I used, below).
My question is: how to make the same but using this.setState?
The below code works, but I am mutating the state directly, and I am receiving the following warning:
Do not mutate state directly. Use setState()
class App extends Component {
constructor(props) {
super(props);
this.state = {
playerState: [
{
name: 'Jack',
hp: 30
},{
name: 'Alan',
hp: 28
}
],
};
}
lowerPlayerHealth = (index) => () => {
this.state.playerState[index].hp = this.state.playerState[index].hp - 1
this.forceUpdate();
}
render() {
return (
<div className="App">
<p>Player 1: {this.state.playerState[0].name}</p>
<p>Health: {this.state.playerState[0].hp}</p>
<button onClick={this.lowerPlayerHealth(0)}>Hit player 1</button>
<p>Player 2: {this.state.playerState[1].name}</p>
<p>Health: {this.state.playerState[1].hp}</p>
<button onClick={this.lowerPlayerHealth(1)}>Hit player 2</button>
</div>
);
}
}
When rendered, it looks like this:
If you want to modify an existing value in the state, you should never get the value directly from the state and update the state object, but rather use the updater function in setState so you can guarantee the state values are the ones you need at the time of updating the state. This is just how React's state works and it's a very common React mistake.
From the official docs
setState() does not always immediately update the component. It may
batch or defer the update until later. This makes reading this.state
right after calling setState() a potential pitfall. Instead, use
componentDidUpdate or a setState callback (setState(updater,
callback)), either of which are guaranteed to fire after the update
has been applied. If you need to set the state based on the previous
state, read about the updater argument below.
setState() will always lead to a re-render unless
shouldComponentUpdate() returns false. If mutable objects are being
used and conditional rendering logic cannot be implemented in
shouldComponentUpdate(), calling setState() only when the new state
differs from the previous state will avoid unnecessary re-renders.
The first argument is an updater function with the signature:
(state, props) => stateChange
state is a reference to the component state at the time the change is
being applied. It should not be directly mutated. Instead, changes
should be represented by building a new object based on the input from
state and props.
Both state and props received by the updater function are guaranteed
to be up-to-date. The output of the updater is shallowly merged with
state.
So you must get the value exactly when you want to update the component inside the setState function using the first argument of the updater function.
lowerPlayerHealth = (index) => () => {
// use setState rather than mutating the state directly
this.setState((state, props) => {
// state here is the current state. Use it to update the current value found in state and ensure that it will be set correctly
return (state); // update state ensuring the correct values
});
}
Solution
To lower a value found in state:
class App extends React.Component {
constructor(props){
super(props);
this.state = {
playerState: [
{
name: 'Jack',
hp: 30
}, {
name: 'Alan',
hp: 28
}
],
};
}
lowerPlayerHealth = (index) => () => {
this.setState((state, props) => {
state.playerState[index].hp -=1; //update the current value found in state
return (state); // update state ensuring the correct values
});
}
render() {
return (
<div className="App">
<p>Player 1: {this.state.playerState[0].name}</p>
<p>Health: {this.state.playerState[0].hp}</p>
<button onClick={this.lowerPlayerHealth(0)}>Hit player 1</button>
<p>Player 2: {this.state.playerState[1].name}</p>
<p>Health: {this.state.playerState[1].hp}</p>
<button onClick={this.lowerPlayerHealth(1)}>Hit player 2</button>
</div>
);
}
}
You've answered your own question: don't mutate state. Also, best practice suggests using the function version of setState.
Since playerState is an array, use Array.map to create a new array containing the same objects, replacing only the one you want to change:
lowerPlayerHealth = (indexToUpdate) => () => {
this.setState(state => ({
...state,
playerState: state.playerState.map(
(item, index) => index === indexToUpdate
? {
...item,
hp: item.hp - 1
}
: oldItem
)
}));
}
If you made playerState an object instead of an array, you can make it tidier:
lowerPlayerHealth = (indexToUpdate) => () => {
this.setState(state => ({
...state,
playerState: {
...state.playerState,
[indexToUpdate]: {
...state.playerState[indexToUpdate],
hp: state.playerState[idToindexToUpdatepdate].hp - 1
}
}
}));
}
I have component Inbox and having checkbox in it... But It works on third click... does not work on first and second click... setState works perfect but it does't re-render after setState
class Inbox extends PureComponent {
constructor(props){
this.state = {
checked: [true]
}
}
updateCheck(i, convId) {
const state = this.state.checked
state[i] = !state[i]
this.setState(state)
}
render() {
return (
<input type="checkbox" checked={this.state.checked[i]} onClick={() => this.updateCheck(i, conv._id)}/>
)
}
}
You are not really updating the state correctly. setting state like
this.setState(state, () => {
console.log(this.state, '787878787878778787')
})
does not update the checked state using state but adds keys with array indices to state like
{0: true, 1: false, conversationId: '', checked: [true, false]};
You are instead mutating the checked state yourself using
state[i] = !state[i]
To update the state correctly, you would write
updateCheck(i, convId) {
const checked = [...this.state.checked]
checked[i] = !checked[i]
this.setState({ checked }, () => {
console.log(this.state, '787878787878778787')
})
}
The problem in your approach arises because you mutate the original state directly, subsequent setState calls may replace the original change and hence you see that behaviour.
According to documentation
Never mutate this.state directly, as calling setState() afterwards may
replace the mutation you made. Treat this.state as if it were
immutable.
Working demo
I've defined my initial state in a Component as follows:
constructor(props) {
super(props);
//this.state = {count: props.initialCount};
this.state = {
timeArray: [],
metawords: '',
description: '',
currentTime: '',
inputFieldsDisabled: true
}
}
I have an event that gets called and I want to just update the metawords property. I was thinking that the following code should work but it does not.
updateInputValue1(evt) {
const newMetawords = "abcd";
this.setState(
[...this.state,{
metawords: evt.target.value
}]
);
Thoughts?
state is an object so updating it the way you are at the moment won't work.
You can simply update only the property you need as:
this.setState({
metawords: evt.target.value,
})
Since you've mentioned spread operator, you can also (but not always necessary) update your state as:
this.setState({
...this.state,
metawords: evt.target.value,
})
or
this.setState(prevState => ({
...prevState,
metawords: evt.target.value,
}))
Should you need more info about it, I recommend you to have a look at ReactJS documentation.
You can use spread operator like this to setState.
this.setState(
{...this.state,
metawords: evt.target.value
})
However since you only want to change a single property, this will also work in your case:
this.setState({metawords: evt.target.value})
Why not simply:
this.setState({ metawords: evt.target.value })
You don't need to pass all other state values, just pass the property and the new value, that you want to update. You don't need to bother about the other state values during setState, React will do the merging (merging of all other state values and the state that you wants to update).
For more details check DOC.
Sorry, I really miss something with the transmission of state within props of sub components in React.
I have implemented a version of a todo list with 3 components.
There is a Form component and a ListTodo component. The state is stored only in the App component.
import React, {Component} from 'react';
import './App.css';
class App extends Component {
constructor(props) {
super(props);
this.state = {
tasks: ["un truc", "autre truc"]
};
this.addTask = this.addTask.bind(this);
}
addTask(task) {
this.setState({
tasks: this.state.tasks.push(task)
})
}
render() {
return (
<div className="App">
<Form onTaskAdded={ this.addTask }></Form>
<ListTodo tasks={ this.state.tasks }></ListTodo>
</div>
);
}
}
class Form extends Component {
constructor(props) {
super(props);
this.state = {
task: ""
}
this.handleChange = this.handleChange.bind(this);
this.addTask = this.addTask.bind(this);
}
handleChange(event) {
this.setState({
task: event.target.value
});
}
addTask(event) {
this.props.onTaskAdded(this.state.task);
event.preventDefault();
}
render() {
return (
<form onSubmit={ this.addTask }>
<input placeholder="À faire" onChange={ this.handleChange }></input>
<input type="submit"></input>
</form>
)
}
}
class ListTodo extends Component {
render() {
const tasks = this.props.tasks.map((t, i) => (
<li key={i}>{t}</li>
))
return (
<ul>{tasks}</ul>
)
}
}
export default App;
The display is good at start so the ListTodo achieves to see the prop tasks. But after a form submission, I get an error on ListTodo.render :
TypeError: this.props.tasks.map is not a function
When I console.log the this.props.tasks, I don't get my array but the length of the array.
Do you know why?
Edit :
Thanks for answers guys, you're right. I missed the behavior of Array.push.
But React seems still odd. If I let the mistaken code
this.setState({
tasks: this.state.tasks.push(task)
})
then a console.log(JSON.stringify(this.state)) displays :
{"tasks":["un truc","autre truc","aze"]}.
Very disturbing to not be able to trust a console.log...
As per MDN DOC:
The push() method adds one or more elements to the end of an array and
returns the new length of the array.
Array.push never returns the result array, it returns the number, so after adding the first task, this.state.tasks becomes a number, and it is throwing the error when you trying to run map on number.
You did the mistake here:
addTask(task) {
this.setState({
tasks: this.state.tasks.push(task)
})
}
Write it like this:
addTask(task) {
this.setState( prevState => ({
tasks: [...prevState.tasks, task]
}))
}
Another import thing here is, the new state will be depend on the previous state value, so instead of using this.state inside setState, use updater function.
Explanation about Edit part:
Two important things are happening there:
1- setState is async so just after setState we can expect the updated state value.
Check this for more details about async behaviour of setState.
2- Array.push always mutate the original array by pushing the item into that, so you are directly mutating the state value by this.state.tasks.push().
Check the DOC for more details about setState.
Check the MDN Doc for spread operator (...).
Check this snippet:
let a = [1,2,3,4];
let b = a.push(5); //it will be a number
console.log('a = ', a);
console.log('b = ', b);
The problem is in how you add a task in the App’s state.
addTask(task) {
this.setState({
tasks: this.state.tasks.push(task)
})
}
See, Array.prototype.push returns the length of the array after adding an element. What you really want is probably Array.prototype.concat.
addTask(task) {
this.setState({
tasks: this.state.tasks.concat([ task ])
})
}
Also, thanks to #t-j-crowder pointers and as also reported by #mayank-shukla, you should use a different approach to mutate your state:
addTask(task) {
this.setState(function (state) {
return {
tasks: state.tasks.concat([ task ])
}
});
}
Or using ES2015 Arrows, Object destructuring and Array spread:
addTask(task) {
this.setState(({ tasks }) => ({
tasks: [ ...tasks, task ]
}));
}
Since Component.prototype.setState can be asynchronous, passing a function to it will guarantee the new state values depend on the right, current previous values.
This means that if two or more setState calls happen one after another you are this way sure that the result of the first one will be kept by applying the second.
As the other answers stated, the Array push method does not return the array. Just to complement the answers above, if you are using ES6, a nice and elegant way of doing this is using the spread operator (you can read more about it here)
this.setState({
tasks: [...this.state.tasks, task]
})
It is essentially the same as using the concat method, but I think this has a nicer readability.
The problem is from Array.push return the number of elements in the array and not the updated array
addTask(task) {
this.setState({
tasks: this.state.tasks.push(task)
})
}
To fix this you can push to state.tasks then setState with it later on:
addTask(task) {
this.state.tasks.push(task);
this.setState({
tasks: this.state.tasks
})
}
This way you set state.task to the updated array.