React - Change a JSON Object in setState - reactjs

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
}
}));

Related

setState with spread operator

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.

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 - Passing State to child component

My Parent Class which holds the overall App state
class App extends Component {
constructor(props) {
super(props);
this.state = {
inputValue: 1,
};
this.handleChange = this.handleChange.bind(this);
};
handleChange(event) {
const target = event.target;
const newValue = target.value;
if( Math.sign(newValue) === 1 ) {
this.setState({
inputValue: newValue
});
}
}
render() {
return (
<EngInput type="number" value={this.state.inputValue} onChange={this.handleChange}/>
);
}
}
My child component to which I am passing the state as props.
class EngInput extends React.Component {
render() {
return (
<input
type="number"
defaultValue={this.props.value}
onChange={this.props.onChange}
/>
);
}
}
The logic I am trying to implement being - The child input component should accept only positive numbers. If its a negative number, the state should not change and the UI should update to the inputValue instead of the newValue.
But, whats happening in the above code is, even though the state does not change to non negative value, the UI still accepts negative value.
How to implement a logic like, if the value is negative, the UI should still show the old positive value from state.
Use value instead of defaultValue to control the input from the parent component.
class EngInput extends React.Component {
render() {
return (
<input
type="number"
value={this.props.value}
onChange={this.props.onChange}
/>
);
}
}

ReactJS Warning: TextField is changing an uncontrolled input of type text to be controlled

I am getting this error below.
Warning: TextField is changing an uncontrolled input of type text to be controlled. Input elements should not switch from uncontrolled to controlled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component.
I am using material-ui.
Here is my code:
class RegistrationForm extends React.Component{
constructor(props) {
super(props)
this.state = { errorText: '', value:this.props }
}
phone(event) {
var strRegExpMobile=/^\d{10}$/;
if (event.target.value.match(strRegExpMobile)) {
this.setState({ errorText: '',
phone:event.target.value
})
} else {
this.setState({ errorText: 'Invalid format' })
}
}
handleSubmit(event){
alert("submit");
var data={
phone:this.state.phone
}
console.log(data)
}
render() {
return (
<div>
<TextField hintText="Phone"
floatingLabelText="Phone"
name="phone"
value={this.state.phone}
errorText= {this.state.errorText}
onChange={this.phone.bind(this)}/>
<RaisedButton label="Submit"
primary={true}
onClick={this.handleSubmit.bind(this)}/>
</div>
)
}
}
Can any one tell where I am wrong?
Reason is, you didn't define the phone in state variable so on initial rendering TextField will be treated as uncontrolled component because value property will have undefined value.
In this line
value = {this.state.phone} => this.state.phone is undefined initially
To fix this define phone in state variable, like this:
constructor(props) {
super(props)
this.state = {
errorText: '',
value:this.props,
phone: ''
}
}
Or define the value property by using Short-circuit evaluation like this:
value = {this.state.phone || ''} //(undefined || '') = ''
Because your value is undefined that's why you are getting this error
Try this
render() {
return (
<div>
<TextField hintText="Phone"
floatingLabelText="Phone"
name="phone"
value={this.state.phone || ''}
errorText= {this.state.errorText}
onChange={this.phone.bind(this)}/>
<RaisedButton label="Submit"
primary={true}
onClick={this.handleSubmit.bind(this)}/>
</div>
)
}

How come I can't type anything into my React search box?

I have a component called SearchInput and it has in it
<input
type='text'
className='input-field'
value={value}
placeholder={this.state.placeholder}
// onFocus={::this.onFocus}
// onBlur={::this.onBlur}
// onKeyUp={::this.onKeyUp}
// onKeyDown={::this.hanleArrowKeys}
// onChange={::this.onChange}
/>
However, when I type anything into it, nothing happens. The text doesn't even appear. What am I doing wrong?
Probably your state is not updating, maybe didn't you bind "this" to function onChange?
Here is an example of correct input in react:
class SearchInput extends Component {
constructor(props) {
super(props);
this.state = { value: '' };
this.handleInputChange = this.handleInputChange.bind(this);
}
handleInputChange(event) {
this.setState({ value: event.target.value });
}
render() {
return <input type="text" value={this.state.value} onChange={this.handleInputChange}/>;
}
}
Probably you are binding the value of the input box with an attribute in the state of the component and you are either not providing an onChange prop to the input tag or your onChange callback method is not updating the attribute in the state to which the input tag's value is bound.

Resources