How to get spread operator updating state with React - javascript

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.

Related

Spread operator in React .setState()

Given the following snippet extracted from a React class component:
constructor(props) {
super(props)
this.state = { active: true }
}
deactivate = () => {
this.setState({ ...this.state, active: false })
}
What is the aim of the spread operator into the stopCounter() method? The application also works removing it:
deactivate = () => {
this.setState({ active: false })
}
Both works in that case, but you don't need to use that. Just setting the state will be okay:
this.setState({active: false})
But let me explain what if you have nested level of states like:
state = {
foo: {
a: 1,
b: 2,
c: 3
}
}
And when you need to update the foo's c state only, then you'll need to merge the state like:
this.setState({ foo: {
...this.state.foo,
c: 'updated value'
}})
So, the spread syntax merges object with later object. It's similar to Object.assign.
The second snippet works because React is implicitly doing the spreading for you. Per React's documentation for setState:
You may [...] pass an object as the first argument to setState(): setState(stateChange[, callback]). This performs a shallow merge of stateChange into the new state.
The goal is just set active to false and keep the rest with no modifications.
You can edit a part of your state just passing the required names, and skipping the rest.

setState not working for array reactjs

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

setState is not updating state properly

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.

Updating React state with Immutable

I'm trying to update my regular React state through Immutable, and got into some few issues. The state isn't deeply nested or it isn't nested from anything other than the state itself, such as { "username" : "keyval" : null}}
This means I could not do something such as username.update('keyval', something), instead I need another approach. Its a rather easy question, I just don't know how to do it. Here's how my setState looks like, which I want to make an Immutable setState action.
handleUpdatePassword(event) {
event.persist()
this.setState(({password}) => ({
password: state.update('password', event.target.value)
})
);
}
And here is the error I get when trying:
handleUpdatePassword(event) {
event.persist()
this.setState({
password: state.update('password', event.target.value)
})
}
Also, This works, but I get this error: this.state.updater is not a function
handleUpdateUsername(event) {
console.log(this.state)
event.persist()
this.setState({
username: this.state.update('username', event.target.value)
})
}
state should be a plain JavaScript object as you can read in the documentation.
Note that state must be a plain JS object, and not an Immutable
collection, because React's setState API expects an object literal and
will merge it (Object.assign) with the previous state.
Your initial state should look something like this
constructor(){
...
this.state = {data: Map({ password: "", username: ""})}
}
After that, you'll be able to update the data like this
handleUpdatePassword(event) {
this.setState(({data}) => ({
data: data.update('password', password => event.target.value)
}));
}
You are creating explicit objects. Just let ImmutableJS do it for you.
class YourReactComp extends React.Component {
constructor() {
this.state = Immutable.Map({"username": ""});
}
handleUpdateUsername(event) {
console.log(this.state)
event.persist()
this.setState(this.state.set("username", event.target.value));
}
}
EDIT
ImmutableMap.update(key, updater) uses a callback to set the value, you want ImmutableMap.set(key, newValue) here.

what is the preferred way to mutate a React state?

Let's say I have a list of plain objects in my this.state.list that I can then use to render a list of children. What then is the right way to insert object into this.state.list?
Below is the only way I think it will work because you can not mutate the this.state directly as mentioned in the doc.
this._list.push(newObject):
this.setState({list: this._list});
This seems ugly to me. Is there a better way?
concat returns a new array, so you can do
this.setState({list: this.state.list.concat([newObject])});
another alternative is React's immutability helper
var newState = React.addons.update(this.state, {
list : {
$push : [newObject]
}
});
this.setState(newState);
setState() can be called with a function as a parameter:
this.setState((state) => ({ list: state.list.concat(newObj) }))
or in ES5:
this.setState(function(state) {
return {
list: state.list.concat(newObj)
}
})
Update 2016
With ES6 you can use:
this.setState({ list: [...this.state.list, ...newObject] });
From the react docs (https://facebook.github.io/react/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous):
Because this.props and this.state may be updated asynchronously, you should not rely on their values for calculating the next state.
So you should do this instead:
this.setState((prevState) => ({
contacts: prevState.contacts.concat([contact])
}));

Categories