ReactJS event handler update state of dictionary value - reactjs

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

Related

React Form single handleChange function with multiple inputs and setState to complex object

I have a form inside a React component that has several inputs, representing properties of an object, defined in the component's state. I would like to make a POST request to my REST API by which I want to create an entity, by passing the filled in object from this.state, which in turn is modified by the form inputs.
I am unable to figure out how to use single handleChange() method to update the state on an onChange event of all the fields. I refereed to articles describing this problem but without complex types. I even tried to implement the dynamic key name by using [event.target.name] and adding name attribute to all of my form inputs but with no result.
Here is my code.
Component constructor and state:
class IngredientForm extends React.Component {
constructor(props) {
super(props);
this.apiService = new apiService();
this.state = {
ingredient: {
id: "",
name: "",
dateAdded: "",
}
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
}
handleChange and handleSubmit methods:
handleChange(event) {
const ingredient = this.state.ingredient;
ingredient[event.target.name] = event.target.value;
this.setState({ ingredient: ingredient });
}
handleSubmit(event) {
//Fetch code goes here
let response = this.apiService.postDataAsync("https://localhost:5001/api/ingredients",this.state.ingredient);
this.setState({ingredient:{id: response.id, dateAdded: response.dateAdded}});
event.preventDefault();
}
And this is the code of the form:
<form onSubmit={this.handleSubmit}>
<label>
Name
</label>
<input
type="text"
name="name"
value={this.state.ingredient.name}
onChange={this.handleChange}
required
/>
<label>Date added</label>
<input
type="date"
name="dateAdded"
value={this.state.ingredient.dateAdded}
onChange={this.handleChange}
/>
<button type="submit" value="Submit">
Save
</button>
</form>
I will be glad to receive your help. I tried to find a similar topic in the forum beforehand, but without success, everyone discussed the situation with primitive types in the state.
hey there the problem is in your set state can you tru this?
handleChange(event) {
const {name, value} = event.target;
this.setState({
ingredient: {
...this.state.ingredient,
[name]: value
}
});
}

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.

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

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