Beginner React Project: How to Build Forms Using Hooks

<img src="https://bomberbot.com/news/content/images/size/w2000/2023/03/React-Hook-Form.png"
alt="React Hook Form"
width="1200px">

Forms are an essential part of most web applications, yet they can be tricky to build and manage. Fortunately, React hooks make working with forms much easier by allowing us to add state and lifecycle features to function components.

In this beginner-friendly guide, we‘ll walk through building a basic registration form in React using hooks. You‘ll learn how to:

  • Create a form with common input types
  • Use the useState hook to manage form state
  • Implement form validation and display errors
  • Handle form submission and show success messages

By the end, you‘ll have a solid understanding of React hooks and be able to build your own forms with confidence. Let‘s get started!

What are React Hooks?

Before we dive into building forms, let‘s briefly review what React hooks are and why they‘re useful.

Hooks were added in React 16.8 as a way to add state and lifecycle methods to function components. Previously, only class components could have state, while function components were considered "stateless".

The most common hooks you‘ll use are:

  • useState: Allows you to add state to a function component
  • useEffect: Performs side effects, like making API requests or subscribing to events
  • useContext: Grants access to React‘s Context API for passing data deeply through the component tree
  • useRef: Provides a way to reference DOM elements or store mutable values

Using hooks, we can make our code more readable, reusable, and testable by separating concerns and avoiding complex class hierarchies. They also make it easier to share stateful logic between components.

Project Setup

To get started, you‘ll need to have Node.js installed on your machine. Create a new React project using Create React App:

npx create-react-app react-form-hooks
cd react-form-hooks

This will set up a basic React project with all the necessary dependencies and configurations. You can start the development server with:

npm start

Now open up src/App.js and replace the contents with:

import React from ‘react‘;

function App() {
  return (
    <div>

    </div>
  );
}

export default App;

We‘ll build our form inside this App component for simplicity, but in a real application you‘d likely have the form in a separate component.

Creating the Form Inputs

Let‘s add some basic inputs to our form for collecting user details. We‘ll need fields for:

  • Name
  • Email
  • Password
  • Confirm Password

Here‘s the JSX for the form:

<form onSubmit={handleSubmit}>
  <div>
    <label htmlFor="name">Name</label>
    <input 
      id="name"
      type="text" 
      name="name"
      value={name}
      onChange={handleNameChange}
      required
    />
  </div>

  <div>
    <label htmlFor="email">Email</label>  
    <input
      id="email"
      type="email"
      name="email"
      value={email}
      onChange={handleEmailChange} 
      required
    />
  </div>

  <div>
    <label htmlFor="password">Password</label>
    <input
      id="password"
      type="password"
      name="password" 
      value={password}
      onChange={handlePasswordChange}
      required
    /> 
  </div>

  <div>
    <label htmlFor="confirmPassword">Confirm Password</label>
    <input
      id="confirmPassword" 
      type="password"
      name="confirmPassword"
      value={confirmPassword}
      onChange={handleConfirmChange}
      required
    />
  </div>

  <button type="submit">Register</button>
</form>

Each input has a label, a unique id that matches its label‘s htmlFor, the appropriate type, a name attribute for form submission, and a value that will be populated from state.

We‘ve also added onChange handlers that will be called whenever the input values change, and required to prevent form submission if any fields are left blank. Finally, there‘s an onSubmit handler on the <form> itself.

But before this will work, we need to add state to keep track of the input values!

Managing Form State

To store the form data and update it as the user types, we‘ll use the useState hook. First, import it at the top:

import React, { useState } from ‘react‘;

Then, declare state variables for each input:

const [name, setName] = useState(‘‘);
const [email, setEmail] = useState(‘‘);  
const [password, setPassword] = useState(‘‘);
const [confirmPassword, setConfirmPassword] = useState(‘‘);

useState returns an array containing the current state value and a function for updating it. We can use array destructuring to unpack those into name and setName, email and setEmail, etc.

Now let‘s implement the change handlers:

function handleNameChange(e) {
  setName(e.target.value);
}

function handleEmailChange(e) {
  setEmail(e.target.value);
}

function handlePasswordChange(e) {
  setPassword(e.target.value); 
}

function handleConfirmChange(e) {
  setConfirmPassword(e.target.value);
}

Each handler receives the event object, reads the input value from e.target.value, and calls the corresponding state setter function.

With this in place, the form inputs will update the component state as the user types. You should be able to fill out the form, although it doesn‘t do anything when submitted yet.

Validating the Form

Before allowing form submission, we should validate the inputs to make sure they meet our requirements. Let‘s add some basic validation checks:

  • Name must not be empty
  • Email must be in a valid format
  • Password must be at least 8 characters
  • Confirm Password must match Password

We can use another state variable to keep track of any validation errors:

const [errors, setErrors] = useState({});

The errors object will have keys corresponding to the input names, with error messages as values. For example:

{
  "email": "Please enter a valid email",
  "confirmPassword": "Passwords do not match"
}

We‘ll validate the form inputs whenever the form is submitted. Here‘s the handleSubmit function:

function handleSubmit(e) {
  e.preventDefault();

  // Validate inputs
  let errors = {};

  if (!name) {
    errors.name = ‘Please enter your name‘;
  }

  if (!email || !isValidEmail(email)) {
    errors.email = ‘Please enter a valid email‘;
  }

  if (!password || password.length < 8) {
    errors.password = ‘Password must be at least 8 characters‘;
  }

  if (password !== confirmPassword) {
    errors.confirmPassword = ‘Passwords do not match‘;
  }

  // If errors, update errors state and return
  if (Object.keys(errors).length > 0) {
    setErrors(errors);
    return;
  }

  // Submit form
  console.log(‘Form submitted!‘);
}

First, we call e.preventDefault() to avoid the default browser behavior of submitting the form and refreshing the page.

Then we create an errors object literal and populate it with any applicable error messages. The email validation uses a helper function called isValidEmail:

function isValidEmail(email) {
  // Basic email format validation
  const emailRegex = /^[^@]+@[^@]+\.[^@]+$/;
  return emailRegex.test(email);
}  

If there are any errors, we update the errors state variable and return early to avoid submitting the form. Otherwise, we‘ll log a message to the console (in a real app, you‘d submit the form data to a server here).

The last step is to display the error messages in the form:

<input 
  id="email"
  type="email" 
  name="email"
  value={email}
  onChange={handleEmailChange}
  required
/>
{errors.email && <span className="error">{errors.email}</span>}

We use the logical && operator to conditionally render the error message span if errors.email has a truthy value. Add similar lines for the other inputs.

Showing Success and Error Messages

After the user submits the form, we should show a success message if the submission was successful, or an error message if something went wrong.

Let‘s add one more state variable to track the submission status:

const [status, setStatus] = useState({ 
  type: ‘‘,
  message: ‘‘ 
});  

The status object will have a type property of either ‘success‘ or ‘error‘, and a corresponding message.

Modify the end of handleSubmit to update the status after a successful submission:

setStatus({
  type: ‘success‘,
  message: ‘Account created successfully!‘
});

// Reset form
setName(‘‘);
setEmail(‘‘);
setPassword(‘‘);
setConfirmPassword(‘‘);
setErrors({}); 

We set a success message and clear out the form state to reset the inputs.

If there was an error submitting the form (e.g. a network error), you‘d catch that and set an error status instead:

setStatus({
  type: ‘error‘,
  message: ‘Something went wrong, please try again later.‘  
});

Finally, display the status message above the form:

{status.type === ‘success‘ && <p className="success">{status.message}</p>}
{status.type === ‘error‘ && <p className="error">{status.message}</p>}

We again use && to conditionally render either the success or error message paragraph based on the status.type.

With that, our form is complete! It should look something like this:

<img src="https://bomberbot.com/news/content/images/2023/03/React-Hook-Form-Complete.png"
alt="Completed React Hook Form"
width="400px">

Enhancing the Form

There are a few more enhancements we can make to improve the user experience of our form.

First, let‘s disable the submit button if the form has validation errors:

<button type="submit" disabled={Object.keys(errors).length > 0}>
  Register
</button>  

The button will be disabled if errors has any properties, preventing submission until the errors are fixed.

We can also add some styling to visually indicate which inputs have errors:

input.error {
  border-color: red;
}

.error {
  color: red;  
  font-size: 0.8em;
  margin-top: 0.5em;
}

Now inputs with errors will have a red border, and error messages will be displayed in red below the input.

As a final touch, let‘s clear the status message when the form is edited after a submission:

useEffect(() => {
  if (status.type) {
    setStatus({ type: ‘‘, message: ‘‘ });
  }
}, [name, email, password, confirmPassword]);

This useEffect hook watches the form state variables, and clears the status whenever they change. That way the success or error message won‘t stick around if the user makes edits.

Next Steps

Congratulations, you now have a fully functional registration form built with React hooks!

Some next steps to continue learning:

  • Extracting the form into a reusable component
  • Implementing more robust validation
  • Submitting the form data to a server
  • Persisting form state on page refresh
  • Creating a multi-step form

You can find the complete source code for this project on GitHub.

I hope this tutorial has helped you understand the basics of building forms with React hooks. Feel free to leave any questions or feedback in the comments below!

For more articles on React and web development, be sure to subscribe to my newsletter. Happy coding!

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *