React Forms Tutorial

Understanding the Problem

We need to create a React component that renders a "Contact Us" form with the following fields:

The form should use controlled inputs with React state to manage the form values.

Devising a Plan

  1. Create a basic ContactUs component with the form structure
  2. Add state variables for each form field
  3. Connect the form inputs to state using controlled inputs
  4. Implement form submission handling
  5. Test the form functionality

Carrying Out the Plan

Step 1: Create the ContactUs Component

First, we'll create a new file called ContactUs.jsx with a basic form structure:

// ContactUs.jsx
function ContactUs() {
  return (
    <div>
      <h2>Contact Us</h2>
      <form>
        <div>
          <label htmlFor="name">Name:</label>
          <input id="name" type="text" />
        </div>
        <div>
          <label htmlFor="email">Email:</label>
          <input id="email" type="text" />
        </div>
        <div>
          <label htmlFor="phone">Phone:</label>
          <input id="phone" type="text" />
        </div>
        <button>Submit</button>
      </form>
    </div>
  );
}

export default ContactUs;

Then update the App component to render our ContactUs component:

// App.jsx
import ContactUs from './ContactUs';

function App() {
  return (
    <ContactUs />
  );
}

export default App;

Step 2: Add State Variables

Now, let's add state variables using the useState hook for each form field:

// ContactUs.jsx
import { useState } from 'react';

function ContactUs() {
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');
  const [phone, setPhone] = useState('');
  const [phoneType, setPhoneType] = useState('');
  const [comments, setComments] = useState('');

  return (
    <div>
      <h2>Contact Us</h2>
      <form>
        <div>
          <label htmlFor="name">Name:</label>
          <input id="name" type="text" />
        </div>
        <div>
          <label htmlFor="email">Email:</label>
          <input id="email" type="text" />
        </div>
        <div>
          <label htmlFor="phone">Phone:</label>
          <input id="phone" type="text" />
        </div>
        <button>Submit</button>
      </form>
    </div>
  );
}

Step 3: Create Controlled Inputs

Next, we'll connect each input to its corresponding state variable using the value attribute and onChange event handler:

// ContactUs.jsx
import { useState } from 'react';

function ContactUs() {
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');
  const [phone, setPhone] = useState('');
  const [phoneType, setPhoneType] = useState('');
  const [comments, setComments] = useState('');

  return (
    <div>
      <h2>Contact Us</h2>
      <form>
        <div>
          <label htmlFor="name">Name:</label>
          <input 
            id="name" 
            type="text" 
            onChange={e => setName(e.target.value)}
            value={name}
          />
        </div>
        <div>
          <label htmlFor="email">Email:</label>
          <input 
            id="email" 
            type="text" 
            onChange={e => setEmail(e.target.value)}
            value={email}
          />
        </div>
        <div>
          <label htmlFor="phone">Phone:</label>
          <input 
            id="phone" 
            type="text" 
            onChange={e => setPhone(e.target.value)}
            value={phone}
          />
        </div>
        <button>Submit</button>
      </form>
    </div>
  );
}

Step 4: Add Phone Type Select and Comments Textarea

Now, let's add the phone type select dropdown and comments textarea field:

// ContactUs.jsx
// ...existing code...

<div>
  <label htmlFor="phone">Phone:</label>
  <input 
    id="phone" 
    type="text" 
    onChange={e => setPhone(e.target.value)}
    value={phone}
  />
  <select
    name="phoneType"
    onChange={e => setPhoneType(e.target.value)}
    value={phoneType}
  >
    <option value="" disabled>
      Select a phone type...
    </option>
    <option>Home</option>
    <option>Work</option>
    <option>Mobile</option>
  </select>
</div>
<div>
  <label htmlFor="comments">Comments:</label>
  <textarea
    id="comments"
    name="comments"
    onChange={e => setComments(e.target.value)}
    value={comments}
  />
</div>

Step 5: Handle Form Submission

Finally, let's implement the form submission handler:

// ContactUs.jsx
import { useState } from 'react';

function ContactUs() {
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');
  const [phone, setPhone] = useState('');
  const [phoneType, setPhoneType] = useState('');
  const [comments, setComments] = useState('');

  const onSubmit = e => {
    // Prevent the default form behavior so the page doesn't reload.
    e.preventDefault();

    // Create a new object for the contact information.
    const contactUsInformation = {
      name,
      email,
      phone,
      phoneType,
      comments,
      submittedOn: new Date()
    };

    console.log(contactUsInformation);

    // Reset the form state.
    setName('');
    setEmail('');
    setPhone('');
    setPhoneType('');
    setComments('');
  };

  return (
    <div>
      <h2>Contact Us</h2>
      <form onSubmit={onSubmit}>
        {/* Form fields here */}
        <button>Submit</button>
      </form>
    </div>
  );
}

Complete ContactUs Component

Here's the complete ContactUs component with all features implemented:

// ContactUs.jsx
import { useState } from 'react';

function ContactUs() {
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');
  const [phone, setPhone] = useState('');
  const [phoneType, setPhoneType] = useState('');
  const [comments, setComments] = useState('');

  const onSubmit = e => {
    // Prevent the default form behavior so the page doesn't reload.
    e.preventDefault();

    // Create a new object for the contact information.
    const contactUsInformation = {
      name,
      email,
      phone,
      phoneType,
      comments,
      submittedOn: new Date()
    };

    console.log(contactUsInformation);

    // Reset the form state.
    setName('');
    setEmail('');
    setPhone('');
    setPhoneType('');
    setComments('');
  };

  return (
    <div>
      <h2>Contact Us</h2>
      <form onSubmit={onSubmit}>
        <div>
          <label htmlFor="name">Name:</label>
          <input 
            id="name" 
            type="text" 
            onChange={e => setName(e.target.value)}
            value={name}
          />
        </div>
        <div>
          <label htmlFor="email">Email:</label>
          <input 
            id="email" 
            type="text" 
            onChange={e => setEmail(e.target.value)}
            value={email}
          />
        </div>
        <div>
          <label htmlFor="phone">Phone:</label>
          <input 
            id="phone" 
            type="text" 
            onChange={e => setPhone(e.target.value)}
            value={phone}
          />
          <select
            name="phoneType"
            onChange={e => setPhoneType(e.target.value)}
            value={phoneType}
          >
            <option value="" disabled>
              Select a phone type...
            </option>
            <option>Home</option>
            <option>Work</option>
            <option>Mobile</option>
          </select>
        </div>
        <div>
          <label htmlFor="comments">Comments:</label>
          <textarea
            id="comments"
            name="comments"
            onChange={e => setComments(e.target.value)}
            value={comments}
          />
        </div>
        <button>Submit</button>
      </form>
    </div>
  );
}

export default ContactUs;

Looking Back and Understanding

Understanding Controlled Components

In this tutorial, we've created what are known as controlled components. Here's what that means:

In HTML, form elements like <input>, <textarea>, and <select> maintain their own internal state. When a user interacts with these elements, they update their own state without any help from JavaScript.

In React, we prefer to keep all state in our component, making it the "single source of truth." We do this by:

  1. Setting the value attribute of form elements to a state variable
  2. Handling the onChange event to update that state variable when the user interacts with the element

This approach gives us complete control over the form's behavior and makes it easier to:

The Flow of Controlled Components

Here's the flow of what happens with controlled components:

  1. A user types a character in an <input> element
  2. The onChange event is triggered
  3. The event handler function is called with the event object
  4. We extract the new value from e.target.value and update our state variable
  5. React re-renders the component with the new state value
  6. The <input> element's value attribute is set to the updated state value

This creates a complete cycle where the component state controls the input value, not the other way around.

Real-World Applications

Forms are essential in web applications. Some common uses include:

The concepts you've learned (controlled inputs, form submission handling) are used in all these scenarios and are foundational for building interactive web applications.

Further Enhancements

Here are some ways you could enhance this form:

  1. Form Validation: Add validation to ensure fields meet certain criteria (e.g., email format, required fields)
  2. Error Messages: Display error messages for invalid inputs
  3. Form Styling: Add CSS to make the form more visually appealing
  4. Loading State: Show a loading indicator during form submission
  5. Success Message: Display a success message after successful submission

Example: Adding Basic Validation

// Add state for validation errors
const [validationErrors, setValidationErrors] = useState({});

// Validate form before submission
const validateForm = () => {
  const errors = {};
  
  if (!name) errors.name = "Name is required";
  if (!email) errors.email = "Email is required";
  if (email && !email.includes('@')) errors.email = "Please enter a valid email";
  if (phone && !phoneType) errors.phoneType = "Phone type is required if phone is provided";
  
  return errors;
};

const onSubmit = e => {
  e.preventDefault();
  
  // Check for validation errors
  const errors = validateForm();
  if (Object.keys(errors).length > 0) {
    setValidationErrors(errors);
    return; // Don't submit if there are errors
  }
  
  // Clear any existing errors
  setValidationErrors({});
  
  // Rest of the submission logic...
};

Then you would display validation errors below each field:

<div>
  <label htmlFor="email">Email:</label>
  <input 
    id="email" 
    type="text" 
    onChange={e => setEmail(e.target.value)}
    value={email}
  />
  {validationErrors.email && (
    <p className="error">{validationErrors.email}</p>
  )}
</div>