Users who visit your web application have a particular goal in mind that they want to accomplish. A form is a medium that allows your users to get in contact with you and send information, such as an order, a catalog request, or even a query, which is passed on to other processes.
A good form design that is clear and smart can help your users to achieve their goal fast. On the contrary, a badly designed form will cause confusion and even discourage users from interacting with your application.
So we agree that a good form benefits your application and make users happy. Yet implementing a good form requirements seems hard in React: dynamic forms, real-time responsive feedback, and creating a nice UX. How do we work these requirements in the land of components
, states
and props
?
The first hint that we can get is of course from React documentation about forms.
handleChange = e => {
this.setState({ value: e.target.value })
}
// ...
<input
onChange={this.handleChange}
value={this.state.value}
/>
This is basically the summary of React's form documentation. It simply tells you that this is how React should be used in handling users click or keystroke. React sets user's value to state and then use that state as value for the input. The end.
Huh? That's it?
Yup. As for the rest of the issues you will face when building form for an application with complex business logic.. well, they are up to you. Like doing:
- Validation
- Displaying errors
- Keeping track of form fields
- Handling submission
As it's read in the documentation, React is very unopinionated about how you might structure your project and choose your library stack. That also means it just provide the very basic necessity in making form components. component
, state
, props
are just like puzzle blocks, and we need to piece them together by ourselves.
Here's the final product you get from this tutorial:
There are 3 basic principles you need to remember when making forms with React, they are:
-
component
is used for rendering form elements, normally JSX elements -
state
is used to keep track of user's inputs -
props
is used for passing data into JSX elements
No matter what kind of form you are trying to create, as long as you remember these 3 basic principles, you'll be fine.
A Basic React Form
Everything in React is a component, including a form, and React used state
to keep track of input values. Here is an example form written in React.
class BasicForm extends Component {
constructor(props) {
super(props);
this.state = {
name:'',
email: '',
};
}
handleNameChange = (event) => {
this.setState({name: event.target.value});
}
handleEmailChange = (event) => {
this.setState({email: event.target.value});
}
handleSubmit = (event) => {
event.preventDefault();
const { name, email } = this.state
alert(`Your state values: \n
name: ${name} \n
email: ${email}`)
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<div className="form-group">
<label htmlFor="name">Name</label>
<input name="name" className="form-control" id="name" placeholder="Enter name" value={this.state.name} onChange={this.handleNameChange} />
</div>
<div className="form-group">
<label htmlFor="email">Email</label>
<input name="email" className="form-control" id="email" placeholder="Enter email" value={this.state.email} onChange={this.handleEmailChange} />
</div>
<button type="submit" className="btn btn-success btn-block">Submit</button>
</form>
);
}
}
Whoa! What does this code do?
Don't worry, the code won't bite! Let me explain them to you now.
We'll start from state
. A react form uses state
as the single source of truth for field values. That means every input
element you'll have on your form component
will take state
value as its value.
this.state = {
name:'',
email: '',
};
State values are then assigned into input
tags value
prop
. We also add an onChange
prop
that will run every time the input value is changed. Lastly, we also add onSubmit
prop into our form component for handling submission.
render() {
return (
<form onSubmit={this.handleSubmit}>
<div className="form-group">
<label htmlFor="name">Name</label>
<input name="name" className="form-control" id="name" placeholder="Enter name"
// value and onChange prop
value={this.state.name}
onChange={this.handleNameChange} />
</div>
<div className="form-group">
<label htmlFor="email">Email</label>
<input name="email" className="form-control" id="email" placeholder="Enter email"
// value and onChange prop
value={this.state.email}
onChange={this.handleEmailChange} />
</div>
<button type="submit" className="btn btn-success btn-block">Submit</button>
</form>
);
}
Next, we can add a handleChange
method which accepts the event
argument. This event object will hold our input name and value.
handleNameChange = (event) => {
this.setState({name: event.target.value});
}
handleEmailChange = (event) => {
this.setState({email: event.target.value});
}
The last part of a form structure is the submit handler method. In this example, we used a handleSubmit
method that simply call an alert box which print out our state values.
handleSubmit = (event) => {
event.preventDefault();
const { name, email } = this.state
alert(`Your state values: \n
name: ${name} \n
email: ${email}`)
}
Like a regular HTML form, this is where the saving or sending data is executed and processed. Since we're using our own JavaScript code to handle submission, we need to add event.preventDefault()
into our submission method. This is because browser's JavaScript listener is set to listen to form submit event, which usually triggers a page reload. By using this preventDefault
, we are telling the browser to stop doing whatever default method it does. That way the page reload will be stopped and our submission method can run.
Making validations
The traditional approach to validate data is by submitting the form, wait for the server to finish validating, then the web page will refresh with some error message. The process takes a lot of time and cumbersome for users.
Since React is a front-end library, it can solve this problem by building instant validation into form component. In fact, this is a common pattern in React application, and it's very awesome in my opinion.
Since React store all form data in the state, we can use a bit of checking before render
and display error message if data isn't valid. For an example, to validate if name length is more than 3 characters, we can use:
render(){
const isValidName = this.state.name.length > 3
const isValidEmail = this.state.email.length > 3
}
Then to put it in context:
// the render method
render() {
const isValidName = this.state.name.length > 3;
const isValidEmail = this.state.email.length > 3;
return (
<form onSubmit={this.handleSubmit}>
<div className="form-group">
<label htmlFor="name">Name</label>
<input
name="name"
className={`form-control ${ isValidName? '':'is-invalid' }`}
id="name"
placeholder="Enter name"
value={this.state.name}
onChange={this.handleNameChange}
/>
{/*feedback here*/}
{ isValidName? null: <div className='invalid-feedback'>Name must be longer than 3 characters</div> }
</div>
<div className="form-group">
{/*after email input*/}
{ isValidEmail? null: <div className='invalid-feedback'>Email must be longer than 3 characters</div> }
</div>
<button type="submit" className="btn btn-success btn-block">
Submit
</button>
</form>
);
}
The form is validating instantly, and the error message will be gone when name is longer than 3 characters. But this validation isn't optimal because we are putting the validation logic into render method, which will turn the method into spaghetti real fast when we are validating lots of data. It also run even before we do anything with the textbox. That's not good.
Using state for error checking
Just as we used state for data entry, we can also use state for validation. We'll add new state property in our state initialization.
this.state = {
name: '',
email: '',
nameError: '',
emailError: ''
}
The formError
state will keep our error message, and we'll use them for displaying any error message we might have. Let's put them into context by creating new validate functions:
handleNameChange = event => {
this.setState({ name: event.target.value }, () => {
this.validateName();
});
};
handleEmailChange = event => {
this.setState({ email: event.target.value }, () => {
this.validateEmail();
});
};
validateName = () => {
const { name } = this.state;
this.setState({
nameError:
name.length > 3 ? null : 'Name must be longer than 3 characters'
});
}
validateEmail = () => {
const { email } = this.state;
this.setState({
emailError:
email.length > 3 ? null : 'Email must be longer than 3 characters'
});
}
With this, only when user type something into the inputs will the validation method run. Now the last thing we have to do is running validation when user clicked on a textbox, and then move to click another textbox without doing anything.
Adding onBlur
Let's add an onBlur
prop to our input elements.
<input
name='name'
// className, id, onChange ...
onBlur={this.validateName}
/>
<input
name='email'
// className, id, onChange ...
onBlur={this.validateEmail}
/>
Now the validation method will run on the corresponding texbox that was "touched" by users, and then display any error message if it has.
Here's the demo again:
Conclusion
Now it's time to wrap what we have learned from this simple example. Let's repeat the 3 basic principles of React form again:
-
component
is used for rendering form elements, normally JSX elements -
state
is used to keep track of user's inputs -
props
is used for passing data into JSX elements
We have seen how this is a tried and true principle of React form. We have written a component
that renders our JSX form. We have used state
to keep track of name
and email
value, and we have used props to pass data from state values into input values, including passing a handleChange
function into onChange
props.
Making form in React is quite a complex task for those still unfamiliar with the way React handles data. If you need some advanced guide on React form, I recommend you to checkout Arinich's high quality React form tutorials. It can help you save lots of time.
Thanks for reading! If you love articles like this, be sure to follow me. I'll be writing more tutorials on React soon.
Top comments (4)
Nice article. i have build a custom hook, which may make developer experience better for form validation.
Github: github.com/bluebill1049/react-hook...
Website: react-hook-form.now.sh
cheers
bill
handleNameChange = event => {
console.log('es me aayi value',event.target.value);
this.setState({ name: event.target.value }, () => {
console.log('sad');
this.validateName not working
Nice article. I would suggest you to have a look into github.com/iusehooks/usetheform .
For some reason, I can't display the error message. I've received it in console though.