We have seen yesterday how to set up a basic form to collect item data that will be sent to a remote API via a POST
request so it can create a new item in a resource collection.
The form we set up was so basic that it didn't even have fields in it, just the form skeleton and a submit button.
A proper form would have input fields so we can enter data and that's what we are going to do now.
We need input fields for name
, price
, imageUrl
, and description
. We are just going to use fields of type "text"
.
Adding an input field
Each input field will have a name
, a value
, a placeholder
and an onChange
property.
Let me show an example first, and we'll go over the details next:
<input
type="text"
name="name"
placeholder="Name"
value={this.state.item.name}
onChange={this.changeHandler}
/>
<input />
is just a React input component with some props on it.
The first prop is the type of input: text in our case.
We then have a name
prop. This is the name of the input field and it's set to the name property of the item we add to the collection.
The placeholder
prop shows some placeholder text in the form field so the user knows what data to enter.
The value prop is more interesting. Its value is set to this.state.item.name
. If you remember, we are in the ItemForm
component, and this component has some state that holds the data we need to add.
Here's what the state object looks like:
state = {
item: {
name: '',
price: '',
imageUrl: '',
description: '',
}
}
this.state.item.name
is currently set to an empty string, so the form field will not display anything in it. It's ready to take whatever the user is going to type in.
onChange prop
The next prop in the input component is onChange
. onChange
is set to a function called changeHandler
.
What is the onChange
prop and why do we need the changeHandler
function?
Every time the user types something into an input field, a browser event is generated.
The onChange
prop tells React to listen to the change event and capture a variety of things, including the character that was typed in.
We need to grab this character, and all the other characters that are typed into the form, so we can save them into the state.
That's how we populate the value of our item object in the state.
This item object will be then sent to the function that actually creates a new item in our collection.
The changeHandler
function
Now, let's look at the changeHandler
function.
Since it's associated with onChange
, the changeHandler
function is fired every time an event happens. So, every time we type a character in the form field, changeHandler
is called.
changeHandler
receives the event that's generated by our typing. It needs to extract the character that was typed in and add it to the correct property in the state.
Here's changeHandler:
changeHandler = event => {
event.persist();
let value = event.target.value;
this.setState(prevState => ({
item: { ...prevState.item, [event.target.name]: value }
}))
};
As you can see, changeHandler
is set to an arrow function that takes the event as an argument.
The event has a target
, which is the input field itself. The input field has a value
, which is the characters typed in at the moment.
So, in the first line of changeHandler
we extract the value of the input field after the event is generated. This will be the character that is typed into the field at the moment.
We assign this value to the value
variable so we can use it in the next statement where we update the state with setState
.
setState
takes the previous state as an argument, it then generates a new object that has all the data in the previous state plus the new value from the form field and replaces the state with this new object that represents the current state.
This operation effectively updates the state in a non destructive way.
Why do we use [event.target.name]
?
One confusing piece of code in this operation may be this:
[event.target.name]: value
What is happening here?
We know what value
is, it's the variable that holds the input field value.[event.target.name]
contains the value of the name
property of the input field.
Here's the input field again:
<input
type="text"
name="name" // <-- event.target.name is set to "name" here
placeholder="Name"
value={this.state.item.name}
onChange={this.changeHandler}
/>
There is a name
prop set to the string "name"
.[event.target.name]
captures this prop value and updates the state object with it. It's like if we said this:
item: {...prevState.item, name: value}
So, why don't we just say name
instead of [event.target.name]
?
The reason is that by using [event.target.name]
we are not tied to one specific input field. We can use this same syntax for any input field that has a name
property.
So, when we have input fields that say:
<input name="description" />
or
<input name="price" />
[event.target.name]
will be set to "description"
and "price"
respectively and update our state object correctly like so:
item: {...prevState.item, description: value}
item: {...prevState.item, price: value}
What we have we done so far.
So far we have added a form input and a function that updates our component state every time a new character is typed into the form field.
Now we need to add some more input fields to our form and send the state data to our parent component so a new item can be created. We will see how to do that in the next article.
I write daily about web development. If you like this article, feel free to share it with your friends and colleagues.
You can receive articles like this in your inbox by subscribing to this newsletter. Just click the button below to sign up for a free subscription.