Handling Forms in React

Ganesh JaiwalGanesh Jaiwal
6 min read

Forms are an essential part of web applications, serving as the primary means for users to submit data. In React, building forms is a common task that comes with its own set of challenges, including state management, validation, and user experience. This blog post will delve into the intricacies of handling forms in React, focusing on controlled vs uncontrolled components, form validation techniques, libraries for form management, and creating dynamic forms.

1. Controlled vs Uncontrolled Components

When building forms in React, one of the first decisions you'll make is whether to use controlled or uncontrolled components. Understanding the difference between the two approaches is crucial for effective form management.

Controlled Components

A controlled component is one where the form data is handled by the React component state. In this setup, React is the single source of truth. Each input field's value is tied to the component's state, meaning any change in input updates the state accordingly.

Example of a Controlled Component:

import React, { useState } from 'react';

const ControlledForm = () => {
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');

  const handleSubmit = (e) => {
    e.preventDefault();
    alert(`Submitting Name: ${name}, Email: ${email}`);
  };

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label>Name:</label>
        <input 
          type="text" 
          value={name} 
          onChange={(e) => setName(e.target.value)} 
        />
      </div>
      <div>
        <label>Email:</label>
        <input 
          type="email" 
          value={email} 
          onChange={(e) => setEmail(e.target.value)} 
        />
      </div>
      <button type="submit">Submit</button>
    </form>
  );
};

In this example, the inputs for name and email are managed through React's useState hooks. The value of each input is bound to its corresponding state variable, and any changes trigger an update to that state.

Uncontrolled Components

In contrast, an uncontrolled component does not keep the form data in state. Instead, you can access the input values by using references (refs). It allows for a more traditional approach for handling forms, similar to how HTML forms work.

Example of an Uncontrolled Component:

import React, { useRef } from 'react';

const UncontrolledForm = () => {
  const nameRef = useRef();
  const emailRef = useRef();

  const handleSubmit = (e) => {
    e.preventDefault();
    alert(`Submitting Name: ${nameRef.current.value}, Email: ${emailRef.current.value}`);
  };

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label>Name:</label>
        <input type="text" ref={nameRef} />
      </div>
      <div>
        <label>Email:</label>
        <input type="email" ref={emailRef} />
      </div>
      <button type="submit">Submit</button>
    </form>
  );
};

In the uncontrolled component example, we use useRef to get the current values of the input fields when the form is submitted. This approach can be beneficial when performance is crucial, as it doesn't involve state updates on every keystroke.

2. Form Validation Techniques

Regardless of whether you're using controlled or uncontrolled components, validating form input is vital. Here are a few popular techniques for form validation in React:

Client-Side Validation

Client-side validation can be implemented manually by checking the validity of each input field when the form is submitted or upon input field changes, thus providing immediate feedback.

Example: Basic Client-Side Validation

const ValidatedForm = () => {
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');
  const [errors, setErrors] = useState({});

  const handleSubmit = (e) => {
    e.preventDefault();
    let formErrors = {};

    if (!name) formErrors.name = 'Name is required';
    if (!email || !/\S+@\S+\.\S+/.test(email)) {
      formErrors.email = 'Email is required and must be valid';
    }

    if (Object.keys(formErrors).length === 0) {
      alert(`Submitting Name: ${name}, Email: ${email}`);
    } else {
      setErrors(formErrors);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label>Name:</label>
        <input 
          type="text" 
          value={name} 
          onChange={(e) => setName(e.target.value)} 
        />
        {errors.name && <p style={{ color: 'red' }}>{errors.name}</p>}
      </div>
      <div>
        <label>Email:</label>
        <input 
          type="email" 
          value={email} 
          onChange={(e) => setEmail(e.target.value)} 
        />
        {errors.email && <p style={{ color: 'red' }}>{errors.email}</p>}
      </div>
      <button type="submit">Submit</button>
    </form>
  );
};

In this example, we've added a simple validation process to check if the name is provided and if the email is in a valid format.

Third-Party Libraries for Validation

For more complex applications, consider using libraries that simplify form validation, such as Yup or Retool. These libraries can work seamlessly with popular form management libraries, making them both powerful and easy to use.

3. Libraries for Form Management

Several popular libraries can help simplify form management in React:

Formik

Formik is one of the most commonly used libraries for handling forms in React. It abstracts away a lot of the boilerplate code needed to manage forms and integrates easily with validation libraries.

Example of Formik:

import React from 'react';
import { Formik, Form, Field, ErrorMessage } from 'formik';
import * as Yup from 'yup';

const FormikForm = () => {
  const validationSchema = Yup.object().shape({
    name: Yup.string().required('Name is required'),
    email: Yup.string().email('Invalid email').required('Email is required')
  });

  return (
    <Formik
      initialValues={{ name: '', email: '' }}
      validationSchema={validationSchema}
      onSubmit={(values) => {
        alert(`Submitting Name: ${values.name}, Email: ${values.email}`);
      }}
    >
      {() => (
        <Form>
          <div>
            <label>Name:</label>
            <Field type="text" name="name" />
            <ErrorMessage name="name" component="div" style={{ color: 'red' }} />
          </div>
          <div>
            <label>Email:</label>
            <Field type="email" name="email" />
            <ErrorMessage name="email" component="div" style={{ color: 'red' }} />
          </div>
          <button type="submit">Submit</button>
        </Form>
      )}
    </Formik>
  );
};

Here, Formik manages the form state and handles submission, while Yup provides validation schema to ensure that the inputs meet specified criteria.

React Hook Form

Another powerful library is React Hook Form, which emphasizes performance and simplicity by relying on hooks. It avoids unnecessary re-renders, making it an excellent choice for larger forms.

Example of React Hook Form:

import React from 'react';
import { useForm } from 'react-hook-form';
import * as yup from 'yup';
import { yupResolver } from '@hookform/resolvers/yup';

const schema = yup.object().shape({
  name: yup.string().required('Name is required'),
  email: yup.string().email('Invalid email').required('Email is required'),
});

const HookForm = () => {
  const { register, handleSubmit, errors } = useForm({
    resolver: yupResolver(schema),
  });

  const onSubmit = (data) => {
    alert(`Submitting Name: ${data.name}, Email: ${data.email}`);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <div>
        <label>Name:</label>
        <input name="name" ref={register} />
        {errors.name && <p style={{ color: 'red' }}>{errors.name.message}</p>}
      </div>
      <div>
        <label>Email:</label>
        <input name="email" ref={register} />
        {errors.email && <p style={{ color: 'red' }}>{errors.email.message}</p>}
      </div>
      <button type="submit">Submit</button>
    </form>
  );
};

In this example, useForm from React Hook Form allows you to register inputs and manage validation errors without the hassle of maintaining complex state.

4. Creating Dynamic Forms

Dynamic forms, or forms that adjust based on user input or external data, can significantly enhance user experience. React makes it easy to create dynamic forms by leveraging state and conditional rendering.

Example of a Dynamic Form:

Let’s create a dynamic form that adds additional input fields based on a user's selection:

import React, { useState } from 'react';

const DynamicForm = () => {
  const [formFields, setFormFields] = useState([{ fieldName: '' }]);

  const handleChange = (index, event) => {
    const newFormFields = [...formFields];
    newFormFields[index].fieldName = event.target.value;
    setFormFields(newFormFields);
  };

  const addField = () => {
    setFormFields([...formFields, { fieldName: '' }]);
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    alert(`Submitting: ${JSON.stringify(formFields)}`);
  };

  return (
    <form onSubmit={handleSubmit}>
      {formFields.map((field, index) => (
        <div key={index}>
          <label>Field {index + 1}:</label>
          <input
            type="text"
            value={field.fieldName}
            onChange={(e) => handleChange(index, e)}
          />
        </div>
      ))}
      <button type="button" onClick={addField}>
        Add Field
      </button>
      <button type="submit">Submit</button>
    </form>
  );
};

In this example, a user can add more fields to the form dynamically. By maintaining an array in the component state, we can control the number and values of the fields displayed in the form.

Conclusion

Handling forms in React involves understanding the balance between controlled and uncontrolled components, implementing robust validation techniques, and leveraging libraries like Formik and React Hook Form to simplify the process. Additionally, learning to create dynamic forms can greatly enhance user experiences.

Whether you're building simple data submission forms or complex, multi-step interfaces, the tools and techniques discussed in this post will help you navigate the intricacies of form handling in React effectively. Happy coding!

0
Subscribe to my newsletter

Read articles from Ganesh Jaiwal directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Ganesh Jaiwal
Ganesh Jaiwal

Hello! I'm a dedicated software developer with a passion for coding and a belief in technology's impact on our world. My programming journey started a few years ago and has reshaped my career and mindset. I love tackling complex problems and creating efficient code. My skills cover various languages and technologies like JavaScript, Angular, ReactJS, NodeJs, and Go Lang. I stay updated on industry trends and enjoy learning new tools. Outside of coding, I cherish small routines that enhance my workday, like sipping tea, which fuels my creativity and concentration. Whether debugging or brainstorming, it helps me focus. When I'm not coding, I engage with fellow developers. I value teamwork and enjoy mentoring newcomers and sharing my knowledge to help them grow. Additionally, I explore the blend of technology and creativity through projects that incorporate art and data visualization. This keeps my perspective fresh and my passion alive. I'm always seeking new challenges, from open-source contributions to hackathons and exploring AI. Software development is more than a job for me—it's a passion that drives continuous learning and innovation.