setState with spread operator - reactjs

I was learning Forms in React and I came across the below code where a single setState method is used for multiple inputs to update the value provided by the user. Can anyone explain what spread operator doing here, what does it hold inside the setState method?
class FormContainer extends Component {
constructor () {
this.state = {
formControls: {
email: {
value: ''
},
name: {
value: ''
},
password: {
value: ''
}
}
}
}
changeHandler = event => {
const name = event.target.name;
const value = event.target.value;
this.setState({
formControls: {
...this.state.formControls,
[name]: {
...this.state.formControls[name],
value
}
}
});
}
render() {
return (
<form>
<input type="email"
name="email"
value={this.state.formControls.email.value}
onChange={this.changeHandler}
/>
<input type="text"
name="name"
value={this.state.formControls.name.value}
onChange={this.changeHandler}
/>
<input type="password"
name="password"
value={this.state.formControls.password.value}
onChange={this.changeHandler}
/>
</form>
);
}
}
export default FormContainer;**
Source: https://medium.com/#agoiabeladeyemi/the-complete-guide-to-forms-in-react-d2ba93f32825

It creates a new object with the a key formControls and the value for that key is composed of all properties that are in this.state.formControls (basically a shallow copy of the old state). The [name]: {...this.state.formControls[name], value} part then overrides the key that equals name with a new object that is composed of the current properties from this.state.formControls[name] and the key value overridden with what is in value:
this.setState({
formControls: {
...this.state.formControls, // shallow copy of current state
[name]: { // overrides key that equals what is in name
...this.state.formControls[name], // shallow copy of current states formControls[name]
value // overrides value
}
}
});
Or simply said it sets a copy of the current state, but with the value in state.formControls[name] changed with what is in value, as the new state.

setState in React should not mutate state. Instead with help of the spread operator a new object is being created containing the old values and the new [name] property.

Related

ReactJS event handler update state of dictionary value

I am trying to write a event handle for few input box and I realize that it's not able to update the state of the dict. if I change it to string it works fine.
if I change state to following it works fine.
this.state = {
firstName: "",
lastName: ""
}
However following doesn't
import React, {Component} from "react"
class App extends Component {
constructor() {
super()
this.state = {
list: {
firstName: "",
lastName: ""
}
}
this.handleChange = this.handleChange.bind(this)
}
handleChange(event) {
const {name, value} = event.target
console.log(name)
this.setState({
[name]: value
})
}
render() {
return (
<form>
<input
type="text"
value={this.state.firstName}
name="list[firstName]"
placeholder="First Name"
onChange={this.handleChange}
/>
<br />
<input
type="text"
value={this.state.lastName}
name="list[lastName]"
placeholder="Last Name"
onChange={this.handleChange}
/>
<h1>{this.state.firstName} {this.state.lastName}</h1>
</form>
)
}
}
export default App
First, you're correctly destructuring the name and value props from event.target in your handleChange function, BUT the name properties you set on your two <input> elements are not intuitive. Your name properties are currently "list[firstName]" and "list[lastName]" -> this won't reach into your this.state.list[firstName] / this.state.list[lastName] properties as you wish - instead, you should change your name properties to reflect your state values, like this:
<input
name="firstName"
{/* other props stay the same... */}
/>
<input
name="lastName"
{/* other props stay the same... */}
/>
Now that your <input> elements have name properties that also match values on your state, you can change your handleChange function to something like this:
handleChange(event) {
// get name and value properties from event target
const {name, value} = event.target
this.setState(prevState => ({
// update your 'list' property
list: {
// spread old values into this object so you don't lose any data
...prevState.list,
// update this field's value
[name]: value
}
}))
}
In your handleChange function you can change setState to following:
this.setState({
list: {
[name]: value
}
})
// in input
value={this.state.list.firstName}
The second case does not work because there are flaws. So to make your code run you need to make two changes in your input field
1) name="list[firstName]" as name="firstName"
2) value={this.state.firstName} as value={this.state.list.firstName}
If you use name="list[firstName]" in your input field then whenever [name]: value in handleChange method executes, it evaluates to ['list[firstName]']: value and it will create another property list[firstName] in the state.
i.e state = { list: {...}, list[firstName]: value }.
So it won't update the property firstName inside the list as you expect.
For more detail: Computed Property Names
And using value={this.state.list.firstName} we can map state list.firstName with the input field
<input
type="text"
// do not use value={this.state.firstName}
value={this.state.list.firstName}
// do not use name="list[firstName]"
name="firstName"
placeholder="First Name"
onChange={this.handleChange}
/>
<input
type="text"
value={this.state.list.lastName}
name="lastName"
placeholder="Last Name"
onChange={this.handleChange}
/>
In your handleChange method, your are trying to update the property firstName and lastName inside list.
So to do that first you need to use list inside this.setState method as this.setState({ list: {...}}).
As list is an object and you want to update specific property of list so first you need to copy all properties inside the list using spread operator. And then after that you can change the property you want to change using dynamic / computed property. So change your handleChange method to
handleChange(event) {
const {name, value} = event.target
this.setState({
list: {
// copy all properties inside the "list"
// so that we change only the property
// we need to change and keep other properties as it is
...this.state.list,
// dynamically changing property
[name]: value
}
})
}

React Form issue with JSON

I have a form with fields formatted like so
<Form.Field>
<input
type="text"
name="firstname"
placeholder="First Name"
value= { this.state.user.firstname }
onChange= { this.onChange }
/>
</Form.Field>
This works fine. My onChange event manages this perfectly.
state = {
user: {
firstname: "",
lastname: ""
}
}
onChange = e => {
this.setState({
user: { ...this.state.user, [e.target.name]: e.target.value }
});
}
Originally, I had attempted to have my JSON object returned from my API with a nested hierarchy around name.
state = {
user: {
name: {
firstname: "",
lastname: ""
}
}
}
But when it's like this, my onChange object adds variables to my state rather than managing the existing ones; even though events fire, I'm not changing the value on the UI. I attempted to navigate the JSON hierarchy by manipulating the name of the form.field to match the JSON but that didn't work:
<Form.Field>
<input
type="text"
name="name.firstname"
placeholder="First Name"
value= { this.state.user.name.firstname }
onChange= { this.onChange }
/>
</Form.Field>
What am I missing?
Your assumption is good, you can't navigate your JSON object in react using this syntax: { [e.target.name]: e.target.value }
The simplest solution is to use a different onChange function that modifies specifically your name sub-object.
If you still want to have a single function, it should be possible to parse the e.target.name value and split the string on . character, but it sounds odd to me.

React - Change a JSON Object in setState

I have a form with three fields, the handleChange method works in the first field (DateOfBirth) but not in the (Id1) and (Id2) fields.
For some reason setState return this error when i try to change the value of the (Id1||Id2) fields.
"A component is changing a controlled input of type text to be uncontrolled. Input elements should not switch from controlled to uncontrolled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component"
import React, { Component } from 'react';
class Form extends React.Component {
constructor(props){
super(props);
this.state = { DateOfBirth:'1990-01-24', Metadata: {Id1:'33813518109', Id2:'John Doe'}}
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
const target = event.target;
const name = target.name;
var value = target.value;
if(name === "Id1" || name === "Id2")
this.setState({Metadata:{[name]: value}});
else
this.setState({[name]: value});
}
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
<input name="DateOfBirth" type="date" onChange={this.handleChange} value={this.state.DateOfBirth} />
<input name="Id1" type="text" onChange={this.handleChange} value={this.state.Metadata.Id1} />
<input name="Id2" type="text" onChange={this.handleChange} value={this.state.Metadata.Id2} />
</form>
</div>
);
}
}
export default Form;
From react docs.
The output of the updater is shallowly merged with prevState.
Which means when you do
// name === 'Id1'
// value === 'dummy'
this.setState({Metadata:{[name]: value}});
then Metadata key in the state will have this shape:
{
Metadata: {
Id1: "dummy"
}
}
Do you see the problem? Now input with Id2 receives as value undefined (this.state.Metadata.Id2 doesn't exist) which will make react throw an error about an uncontrolled component.
What you need to do to fix it is to make a full copy of nested object properties:
this.setState(prevState => ({
Metadata:{
...prevState.Metadata,
[name]: value
}
}));

React: why binding value makes input readonly?

given this initial state:
this.state = {
person: { id: 1, name: "Liero" }
};
when I bind input like this: <input value={this.state.person.name} />
Then I makes the input non editable. I understand that it would make state and DOM element out of sync.
However, when I use defaultValue: <input defaultValue={this.state.person.name} />,
I loose posibility to change the person's name from code (e.g. when I want to reset the form).
When I manually sync the state and input value:
<input value={this.state.person.name}
onChange={(evt) => this.state.person.name = evt.target.value }/>
it does not work until I call setState, or force refresh?
Do I really need to force render of entire component each time the input value changes, when I want to have the posibility to control the person.name from code?
Instead of set this.state.person.name directly. call this.setState.
This will trigger another render cycle and then bind this.state.person.name to value:
<input value={this.state.person.name}
onChange={(evt) => {
this.state.person.name = env.target.value;
this.setState({person:this.state.person});
}}/>
Did you mean to setState() instead of mutate the state? like this:
class MyComponent extends Component {
state = { person: { id: 1, name: "Liero" } }
updateName = (e) => {
this.setState({
person: {...this.state.person, name: e.target.value}
})
}
render (){
return (
<input type="text" onChange={this.updateName} value={this.state.person.name} />
);
}
}

How to allow updates to input without updating props in React?

I would like to allow a user to enter changes in an input field without propagating them to the parent. I did this by returning out of the onChange function whenever I don't want to propagate. However this seems to undo the character I typed.
Here is a use case. I have a number field. I want to trigger onChange on parent when there is a number entered, but ignore "." and ","s (formatters.staticToFloat removes them).
export default class NumberField extends React.Component {
render () {
var props = this.props;
var format = props.formatter || formatters.number;
return (
<div>
<label>{props.inputLabel}</label>
<input
type="text"
name={props.name}
onChange={this.onChange.bind(this)}
value={!_.isUndefined(props.value) ? format(props.value) : null}
/>
</div>
);
}
onChange (e) {
var numValue = formatters.stringToFloat(e.target.value);
//they added a . or , we don't propagate change
if (this.props.value === numValue) {
return;
}
if (this.props.onChange) {
this.props.onChange({
value: numValue,
valid: validation.isValid(numValue, this.props.validation)
});
}
}
};
So far the best way I've come up with is maintaining a separate formattedValue state that only gets set when I want to override the default formatting. It works, but seems like a super dirty solution.
export default class NumberField extends React.Component {
constructor () {
super();
this.state = {
formattedValue: null
};
}
render () {
var props = this.props;
var state = this.state;
var format = props.formatter || formatters.number;
var inputValue = state.formattedValue || (
!_.isUndefined(props.value) ?
format(props.value) :
null
);
return (
<div>
<label>{props.inputLabel}</label>
<input
type="text"
name={props.name}
onChange={this.onChange.bind(this)}
value={inputValue}
/>
</div>
);
}
onChange (e) {
var numValue = formatters.stringToFloat(e.target.value);
//they added a . or , we don't propagate change
if (this.props.value === numValue) {
this.setState({
formattedValue: e.target.value
});
} else {
this.setState({
formattedValue: null
});
}
if (this.props.onChange) {
this.props.onChange({
value: numValue,
valid: validation.isValid(numValue, this.props.validation)
});
}
}
};
Sounds like a case for using state to store the formatted value inside the component, and use a special variant of setState with a callback.
all user input (including . and ,) is put in state and rendered in input field back to user.
only when the format is correct, the parent onChange() is called.
The parent may actually do a re-render triggered by the onChange() call. Therefore, we need to make sure that the last entered character is updated in state, and only after that the parent's onChange() will be called.
Your component would look like as follows:
export default class NumberField extends React.Component {
constructor (props) {
super(props);
this.state = {
value: !_.isUndefined(props.value) ?
props.formatter ? props.formatter(props.value).toString() : formatters.number(props.value).toString()
: null;
};
}
render () {
var props = this.props;
var state = this.state;
return (
<div>
<label>{props.inputLabel}</label>
<input
type="text"
name={props.name}
onChange={this.onChange.bind(this)}
value={state.value}
/>
</div>
);
}
onChange (e) {
var numValue = formatters.stringToFloat(e.target.value);
// if numValue is different from current state
// then it must be an OK update,
// so we update state AND call parent if function exists
if (numValue.toString() != this.state.value) {
this.setState({
value: numValue
},
this.callParent // here is the magic: we pass a callback, to be called after state update and after re-render
);
} else {
// otherwise we only update state (to display invalid character)
this.setState({
value: numValue
});
}
}
callParent() {
// state is updated and component has re-rendered when this is called
// so we can use state.value to inform parent
if (this.props.onChange) {
this.props.onChange({
value: this.state.value,
valid: validation.isValid(this.state.value, this.props.validation)
});
}
}
};
This may be a bit of overkill: you keep a state (formatted value) that you also communicate to the parent. If your parent ALWAYS passes down the newly forwarded input, then you could make your component a lot simpler:
No state, but simply re-render based on props.
You only really need state if the value presented to the user in the input field can deviate from what you communicate to parent.

Resources