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.
Related
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
}
});
}
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.
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
}
})
}
I came across the arrow function feature being used as Class property in React component. Looking online I found that it makes code more readable and because of the arrow function features we do not have to bind the handlEvents function inside of constructor.
I still have to use the bind method even while using an arrow function for class property as shown in the code below. When i remove the binding in constructor it shows error in console Warning: A component is changing an uncontrolled input of type text to be controlled. and the form errors do not show up as well
class Contact extends Component {
constructor(props) {
super(props);
this.handleBlur = this.handleBlur(this);
}
handleBlur = evt => field => {
this.setState({
touched: { ...this.state.touched, [field]: true }
});
render() {
return(
<Form onSubmit={this.handleSubmit}>
<FormGroup row>
<Label htmlFor="firstname" md={2}>
First Name
</Label>
<Col md={10}>
<Input
type="text"
id="firstname"
name="firstname"
placeholder="First Name"
valid={errors.firstname === ""}
invalid={errors.firstname !== ""}
value={this.state.firstname}
onBlur={event => {
this.handleBlur("firstname");
}}
onChange={this.handleInputChange}
/>
<FormFeedback>{errors.firstname}</FormFeedback>
</Col>
</FormGroup>
</Form>
)
}
Arrow functions for early bindings in classes are not officially supported by the current ECMAScript.
Using arrow functions as class methods will get you in trouble when your class is inherited and the child wants to override a parent method.
However, I would say it is pretty safe to use them in your react components as you will not get into trouble with inheritance here, since with react you usually will not further inherit from your own components (see Composition vs Inheritance):
At Facebook, we use React in thousands of components, and we haven’t found any use cases where we would recommend creating component inheritance hierarchies.
Dan Abramov is using arrow functions in component methods as well, however he recommends only to use it if early binding is required.
While it’s still experimental, in my experience it solves the problem fairly nicely. It’s not at all React-specific: I find it useful in any classes that deal with asynchrony and callbacks because the binding problem is common for all JavaScript, not just React. We enabled this syntax proposal in the whole Facebook codebase, and if it gets dropped or changes, we’ll make sure to release an automated codemod to migrate to the new syntax (or, worst case, transform it back into bind calls in constructor).
However as Dan notes, to be on the safe site, stick to early binding in constructors:
If you want to stick to the language standard, manual binding in
constructor is the way to go. It’s tedious but usually you only want
to do this for event handlers, and by convention you start them with
handle* in React, so it’s not too hard to remember to bind those.
Update: regarding your case:
In your case you can either use the solution provided by Anshul Bansal where you pass the fieldname into your handleBlur and make use of the field variable in your closure when you pass the returned function as event callback.
Or you can directly acces the input name of the field via the evt.target (code not tested).
handleBlur = evt => {
const field = evt.target.name;
this.setState({
touched: { ...this.state.touched, [field]: true }
});
You need to change the function a little bit as follow.
class Contact extends Component {
constructor(props) {
super(props);
this.handleBlur = this.handleBlur(this);
}
handleBlur = field => () => {
this.setState({
touched: { ...this.state.touched, [field]: true }
});
render() {
return(
<Form onSubmit={this.handleSubmit}>
<FormGroup row>
<Label htmlFor="firstname" md={2}>
First Name
</Label>
<Col md={10}>
<Input
type="text"
id="firstname"
name="firstname"
placeholder="First Name"
valid={errors.firstname === ""}
invalid={errors.firstname !== ""}
value={this.state.firstname}
onBlur={this.handleBlur("firstname")}
onChange={this.handleInputChange}
/>
<FormFeedback>{errors.firstname}</FormFeedback>
</Col>
</FormGroup>
</Form>
)
}
I would not do it with an arrow function, but you can. I will explain the two methods (there are a few more), the first is the one that I use normally.
Binding with a higher order function (or method)
It is simply a method that returns the event callback, as this is a method is already bound to this. This way you can pass any arguments to the method that is a closure, and these arguments will be present in the callback. That is the case of the field argument. Note that I switched the order of the argument, field should be first because it is called first to return the callback.
handleBlur(field) {
return evt => {
console.log(this.state);
this.setState({
touched: { ...this.state.touched,
[field]: true
}
});
};
}
And you can bind it simply with:
onBlur = {this.handleBlur("firstname")}
This has the advantage that you do not need to bind to this in the constructor.
Using an arrow function
The code is similar, but you have to bind to this in the constructor.
handleBlurArrow = field => evt => {
console.log(this.state);
this.setState({
touched: { ...this.state.touched,
[field]: true
}
});
};
Binding:
onBlur = {this.handleBlurArrow("firstnameArrow")}
Bind this on constructor:
this.handleBlurArrow = this.handleBlurArrow.bind(this);
Working example
class Contact extends React.Component {
constructor(props) {
super(props);
this.state = {};
this.handleBlurArrow = this.handleBlurArrow.bind(this);
}
handleBlurArrow = field => evt => {
console.log(this.state);
this.setState({
touched: { ...this.state.touched,
[field]: true
}
});
};
handleBlur(field) {
return evt => {
console.log(this.state);
this.setState({
touched: { ...this.state.touched,
[field]: true
}
});
};
}
render() {
return (<div>
<input type = "text" id = "firstname"
name = "firstname"
placeholder = "First Name"
value = {this.state.firstname}
onBlur = {this.handleBlur("firstname")}
onChange = {this.handleInputChange}
/>
<input type = "text" id = "firstnameArrow"
name = "firstname"
placeholder = "First Name Arrow"
value = {this.state.firstname}
onBlur = {this.handleBlurArrow("firstnameArrow")}
onChange = {this.handleInputChange}
/>
</div>
)
}
}
ReactDOM.render( <Contact /> ,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
I make an Ajax request that returns me an object like this :
Object { user1: "darkblue", user2: "darkred" }
How can I store this object in the component's state since none of the following works :
this.state = { usersColors: '' } // use for strings
this.state = { usersColors: [] } // used for arrays
Which syntax use for storing objects?
To initialise any variable as object, write it like this:
this.state = { usersColors: {} }
And use setState to update the state once you get the response, like this:
this.setState({usersColors: response})
Now you can access the response values by:
this.state.usersColors.user1 ----> "darkblue"
this.state.usersColors.user2 ----> "darkred"
Update:
Check this snippet:
let state = {
userColors: {
user1: 'red',
user2: 'blue'
}
}
let username = 'user1';
console.log('user1 value = ', state.userColors[username]);
You should never use this.state = ... anywhere except in a class constructor. Instead use this.setState({usersColors: {myObj: foo, etc: bar}}) One caveat: don't use this.setState in the render() method, you will just get an error.
How to map the different html elements who are going to be the part of the same array object to the object in the components state. Like if i have following two html elements who are part of a single object user
<div className="form-group">
<input
className="form-control"
type="text"
name="user[username]"
value={this.state.user.username}
onChange={this.onChange}
placeholder="Customer Name"
/>
</div>
<div className="form-group">
<input
className="form-control"
type="text"
name="user[mobilenumber]"
value={this.state.user.mobilenumber}
onChange={this.onChange}
placeholder="Customer mbole no."
/>
</div>
What should i give the name to these two elements such that they get mapped to the user object in the state present in the constructor
this.state = {
user: { username: "", mobilenumber: "" },
clothname: "",
description: "",
errors: {}
};
ANd with the following simple onChange function
onChange(event) {
this.setState({ [event.target.name]: event.target.value });
}