intro-frontend-course

Forms and Controlled Components in React


1. Introduction to Forms and Controlled Components

Forms are an essential part of any web application, enabling user input and interaction. In React, handling forms is often done using controlled components, where form data is managed by React state. Understanding controlled components is key to building dynamic and interactive forms.

Key Concepts:

Basic Example of a Controlled Component:
function App() {
  const [value, setValue] = React.useState('');

  const handleChange = (event) => {
    setValue(event.target.value);
  };

  return (
    <form>
      <label>
        Name:
        <input type="text" value={value} onChange={handleChange} />
      </label>
      <p>Your input: {value}</p>
    </form>
  );
}

In this example, the input field’s value is controlled by the React state value. As the user types, handleChange updates the state, and the input field is re-rendered to reflect the new value.


2. Deep Dive into Form Handling with Controlled Components

Controlled components offer fine-grained control over form elements and are crucial for implementing features like validation, dynamic form fields, and more.

2.1. Handling Multiple Form Inputs

When dealing with multiple form inputs, it’s efficient to manage them within a single state object rather than creating separate state variables for each input.

Example:

function App() {
  const [formValues, setFormValues] = React.useState({
    firstName: '',
    lastName: '',
    email: ''
  });

  const handleChange = (event) => {
    const { name, value } = event.target;
    setFormValues({
      ...formValues,
      [name]: value
    });
  };

  return (
    <form>
      <label>
        First Name:
        <input type="text" name="firstName" value={formValues.firstName} onChange={handleChange} />
      </label>
      <label>
        Last Name:
        <input type="text" name="lastName" value={formValues.lastName} onChange={handleChange} />
      </label>
      <label>
        Email:
        <input type="email" name="email" value={formValues.email} onChange={handleChange} />
      </label>
      <p>Your input: {JSON.stringify(formValues)}</p>
    </form>
  );
}

In this example, the state is an object that holds all form values, making it easier to manage and update multiple fields.

2.2. Handling Form Submission

React forms often require a custom submit handler to process the form data. During submission, you can prevent the default form action and handle the data using React state.

Example:

function App() {
  const [formValues, setFormValues] = React.useState({
    firstName: '',
    lastName: '',
    email: ''
  });

  const handleChange = (event) => {
    const { name, value } = event.target;
    setFormValues({
      ...formValues,
      [name]: value
    });
  };

  const handleSubmit = (event) => {
    event.preventDefault();
    alert(`Form submitted: ${JSON.stringify(formValues)}`);
  };

  return (
    <form onSubmit={handleSubmit}>
      <label>
        First Name:
        <input type="text" name="firstName" value={formValues.firstName} onChange={handleChange} />
      </label>
      <label>
        Last Name:
        <input type="text" name="lastName" value={formValues.lastName} onChange={handleChange} />
      </label>
      <label>
        Email:
        <input type="email" name="email" value={formValues.email} onChange={handleChange} />
      </label>
      <button type="submit">Submit</button>
    </form>
  );
}

Here, the handleSubmit function prevents the default form submission and instead handles the form data within React, allowing for additional processing or validation before submission.


3. Handling Complex Forms

As forms become more complex, you might need to implement features like validation, dynamic form fields, and managing large forms.

3.1. Form Validation

Validation ensures that the data entered by the user is correct and complete. In React, validation can be implemented at the state level.

Example with Simple Validation:

function App() {
  const [formValues, setFormValues] = React.useState({
    email: '',
    password: ''
  });
  const [errors, setErrors] = React.useState({});

  const handleChange = (event) => {
    const { name, value } = event.target;
    setFormValues({
      ...formValues,
      [name]: value
    });
  };

  const validate = () => {
    const newErrors = {};
    if (!formValues.email.includes('@')) {
      newErrors.email = 'Email must include "@"';
    }
    if (formValues.password.length < 6) {
      newErrors.password = 'Password must be at least 6 characters long';
    }
    return newErrors;
  };

  const handleSubmit = (event) => {
    event.preventDefault();
    const validationErrors = validate();
    if (Object.keys(validationErrors).length > 0) {
      setErrors(validationErrors);
    } else {
      alert(`Form submitted: ${JSON.stringify(formValues)}`);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <label>
        Email:
        <input type="email" name="email" value={formValues.email} onChange={handleChange} />
        {errors.email && <p>{errors.email}</p>}
      </label>
      <label>
        Password:
        <input type="password" name="password" value={formValues.password} onChange={handleChange} />
        {errors.password && <p>{errors.password}</p>}
      </label>
      <button type="submit">Submit</button>
    </form>
  );
}

In this example, the form validates the email and password fields before submission. If there are validation errors, they are displayed to the user.

3.2. Dynamic Form Fields

Dynamic form fields allow users to add or remove input fields as needed. This is useful in scenarios like adding multiple addresses or phone numbers.

Example with Dynamic Fields:

function App() {
  const [inputFields, setInputFields] = React.useState([{ value: '' }]);

  const handleChange = (index, event) => {
    const values = [...inputFields];
    values[index].value = event.target.value;
    setInputFields(values);
  };

  const handleAddField = () => {
    setInputFields([...inputFields, { value: '' }]);
  };

  const handleRemoveField = (index) => {
    const values = [...inputFields];
    values.splice(index, 1);
    setInputFields(values);
  };

  const handleSubmit = (event) => {
    event.preventDefault();
    alert(`Form submitted: ${JSON.stringify(inputFields)}`);
  };

  return (
    <form onSubmit={handleSubmit}>
      {inputFields.map((inputField, index) => (
        <div key={index}>
          <input
            type="text"
            value={inputField.value}
            onChange={(event) => handleChange(index, event)}
          />
          <button type="button" onClick={() => handleRemoveField(index)}>
            Remove
          </button>
        </div>
      ))}
      <button type="button" onClick={handleAddField}>
        Add Field
      </button>
      <button type="submit">Submit</button>
    </form>
  );
}

In this example, users can add or remove input fields dynamically. The state management is handled through an array of field values.

3.3. Handling Large Forms with Libraries

For very large or complex forms, managing state manually can become cumbersome. Libraries like Formik or React Hook Form provide abstractions that simplify form management, validation, and submission.

Example with Formik:

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

function App() {
  return (
    <Formik
      initialValues=
      validationSchema={Yup.object({
        email: Yup.string().email('Invalid email address').required('Required'),
        password: Yup.string().min(6, 'Must be at least 6 characters long').required('Required')
      })}
      onSubmit={(values, { setSubmitting }) => {
        alert(JSON.stringify(values, null, 2));
        setSubmitting(false);
      }}
    >
      {({ isSubmitting }) => (
        <Form>
          <label htmlFor="email">Email</label>
          <Field name="email" type="email" />
          <ErrorMessage name="email" />

          <label htmlFor="password">Password</label>
          <Field name="password" type="password" />
          <ErrorMessage name="password" />

          <button type="submit" disabled={isSubmitting}>
            Submit
          </button>
        </Form

>
      )}
    </Formik>
  );
}

In this example, Formik simplifies form handling and validation with minimal boilerplate.


4. Video Resources

To reinforce your understanding of form handling and controlled components in React, here are some helpful videos:

Controlled vs Uncontrolled Components in React (8:30)
Form Validation in React (Formik and Yup) (12:15)
React Forms - Dynamic Form Fields (9:45)

5. External Resources

For further reading and exploration: