Skip to Content
Lesson 8

Basic Forms

Workplace Context

In nearly every web application, users need to provide input. Whether it is logging in, submitting contact information, configuring settings, or creating content, forms are the primary mechanism for collecting user data. As a React developer, understanding how to build robust and interactive forms is a fundamental skill. React provides a powerful way to manage form state and handle user input efficiently using the “controlled components” pattern.


Learning Objectives

By the end of this lesson, you will be able to:

  • Explain the concept of controlled components in React forms.
  • Implement controlled inputs for various types (text, textarea, checkbox, select).
  • Handle form submission using the onSubmit event handler.
  • Prevent the default form submission behavior.
  • Manage form state using the useState hook for single and multiple inputs.
  • Apply TypeScript for type safety in form elements and state.

Understanding Controlled Components

In standard HTML, form elements like <input>, <textarea>, and <select> typically maintain their own state internally and update it based on user input. When you type into an input field, the browser handles the state update.

In React, the “controlled component” pattern flips this model. Instead of the DOM managing the input’s state, the React component’s state becomes the single source of truth.

  1. State Manages Value: The value of the form input (e.g., what is typed in an <input>) is driven by the React component’s state. We use the value prop on the input element and link it to a state variable.
  2. State Updates on Change: Any change to the input (e.g., typing a character) triggers an onChange event. We provide an event handler function to this onChange prop.
  3. Handler Updates State: The onChange event handler updates the React component’s state with the new value from the input.
  4. Re-render: React re-renders the component, and the input element receives the updated value from the state via its value prop.

This creates a loop: State -> Input Value -> Change Event -> Handler -> Update State -> Re-render. This gives React full control over the form element’s data.


Implementing a Simple Controlled Input

Let’s start with a basic text input.

Editor
Loading...
Preview

Explanation:

  1. We initialize a state variable name using useState('').
  2. The <input> element’s value prop is bound to the name state variable.
  3. The onChange prop is set to the handleChange function.
  4. handleChange receives the event object (React.ChangeEvent<HTMLInputElement>). We use event.target.value to get the current value of the input and update the name state using setName.
  5. As the state changes, the component re-renders, and the input displays the value stored in the name state.

Handling Different Input Types

The controlled component pattern applies similarly to other form elements.

Textarea

// Example: Controlled Textarea import React, { useState } from 'react'; const TextAreaForm: React.FC = () => { const [message, setMessage] = useState<string>(''); const handleTextAreaChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => { setMessage(event.target.value); }; return ( <form> <label htmlFor="messageArea">Message:</label> <textarea id="messageArea" value={message} onChange={handleTextAreaChange} rows={4} /> <p>Message length: {message.length}</p> </form> ); }; export default TextAreaForm;

Note that for <textarea>, we use the value prop, just like <input>, instead of setting content between the tags as in plain HTML. The event type is React.ChangeEvent<HTMLTextAreaElement>.

Select Dropdown

// Example: Controlled Select import React, { useState } from 'react'; const SelectForm: React.FC = () => { const [selectedValue, setSelectedValue] = useState<string>('option2'); // Default value const handleSelectChange = (event: React.ChangeEvent<HTMLSelectElement>) => { setSelectedValue(event.target.value); }; return ( <form> <label htmlFor="optionsSelect">Choose an option:</label> <select id="optionsSelect" value={selectedValue} onChange={handleSelectChange}> <option value="option1">Option 1</option> <option value="option2">Option 2</option> <option value="option3">Option 3</option> </select> <p>Selected: {selectedValue}</p> </form> ); }; export default SelectForm;

For <select>, the value prop on the select element corresponds to the value of the selected option. The event type is React.ChangeEvent<HTMLSelectElement>.

Checkbox

Checkboxes are slightly different because their state is usually boolean (true/false), and we check the checked property instead of value.

// Example: Controlled Checkbox import React, { useState } from 'react'; const CheckboxForm: React.FC = () => { const [isChecked, setIsChecked] = useState<boolean>(false); const handleCheckboxChange = (event: React.ChangeEvent<HTMLInputElement>) => { setIsChecked(event.target.checked); // Use event.target.checked }; return ( <form> <label> <input type="checkbox" checked={isChecked} // Use 'checked' prop onChange={handleCheckboxChange} /> Agree to terms </label> <p>Agreed: {isChecked ? 'Yes' : 'No'}</p> </form> ); }; export default CheckboxForm;

We use the checked prop linked to state and event.target.checked in the handler.


Handling Form Submission

Forms typically have a submit button. In many practical cases, we need to handle the submission event to process the collected data (e.g., send it to a server).

// Example: Handling Form Submission import React, { useState } from 'react'; const SubmitForm: React.FC = () => { const [username, setUsername] = useState<string>(''); const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => { setUsername(event.target.value); }; const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => { event.preventDefault(); // VERY IMPORTANT! alert(`Submitting username: ${username}`); // Here you would typically send the data to an API // e.g., fetch('/api/users', { method: 'POST', body: JSON.stringify({ username }) }) }; return ( <form onSubmit={handleSubmit}> {/* Attach handler to form's onSubmit */} <label htmlFor="usernameInput">Username:</label> <input type="text" id="usernameInput" value={username} onChange={handleChange} /> <button type="submit">Submit</button> </form> ); }; export default SubmitForm;

Key Points:

  1. onSubmit on <form>: The event handler (handleSubmit) is attached to the <form> element’s onSubmit prop, not the button’s onClick. This catches submissions triggered by pressing Enter in an input field as well.
  2. event.preventDefault(): This is crucial. By default, submitting an HTML form causes the browser to navigate to a new page or refresh the current one. event.preventDefault() stops this default behavior, allowing our React code to handle the submission logic within the single-page application flow.
  3. Event Type: The event type for form submission is React.FormEvent<HTMLFormElement>.

Managing State for Multiple Inputs

Forms often have multiple fields. How do we manage state for all of them?

Strategy 1: Individual useState Calls

Use a separate useState hook for each form field. This is simple and clear for smaller forms.

// Example: Multiple useState import React, { useState } from 'react'; const MultiStateForm: React.FC = () => { const [firstName, setFirstName] = useState<string>(''); const [lastName, setLastName] = useState<string>(''); const [email, setEmail] = useState<string>(''); const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => { event.preventDefault(); console.log('Submitting:', { firstName, lastName, email }); // API call logic... }; return ( <form onSubmit={handleSubmit}> <label>First Name:</label> <input type="text" value={firstName} onChange={e => setFirstName(e.target.value)} /> <label>Last Name:</label> <input type="text" value={lastName} onChange={e => setLastName(e.target.value)} /> <label>Email:</label> <input type="email" value={email} onChange={e => setEmail(e.target.value)} /> <button type="submit">Register</button> </form> ); };

Strategy 2: Single State Object

Use a single useState hook with an object containing all form fields. This can be more organized for larger forms but requires careful updating as highlighted below.

// Example: Single State Object import React, { useState } from 'react'; interface FormData { firstName: string; lastName: string; email: string; } const SingleStateForm: React.FC = () => { const [formData, setFormData] = useState<FormData>({ firstName: '', lastName: '', email: '', }); const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => { const { name, value } = event.target; // Destructure name and value setFormData(prevFormData => ({ ...prevFormData, // Spread existing state [name]: value // Update changed field using computed property name })); }; const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => { event.preventDefault(); console.log('Submitting:', formData); // API call logic... }; return ( <form onSubmit={handleSubmit}> <label>First Name:</label> {/* IMPORTANT: Add 'name' attribute matching the state key */} <input type="text" name="firstName" value={formData.firstName} onChange={handleChange} /> <label>Last Name:</label> <input type="text" name="lastName" value={formData.lastName} onChange={handleChange} /> <label>Email:</label> <input type="email" name="email" value={formData.email} onChange={handleChange} /> <button type="submit">Register</button> </form> ); };

Key Aspects of Single State Object:

  1. name Attribute: Each input must have a name attribute that matches the corresponding key in the state object (formData).
  2. Generic handleChange: We can use a single handleChange function for all inputs. It uses event.target.name to determine which field to update.
  3. Immutable Update: Inside setFormData, we use the spread operator (...prevFormData) to copy the existing state and then update only the changed field using computed property names ([name]: value). This ensures we do not mutate the state directly.

The choice between these strategies depends on the complexity of the form and personal/team preference.


Activity 1: Build a Contact Form

Setup

Use the editor below, which provides a basic React + TypeScript setup, or set up a similar environment on your own.

Task

Build a simple contact form component (ContactForm) with the following fields:

  • Name (text input)
  • Email (email input)
  • Message (textarea)
  • A “Send Message” submit button

Implement the form using the controlled components pattern. Choose either the individual useState or single state object strategy. When the form is submitted:

  1. Prevent the default form submission.
  2. Use console.log to output the collected form data (name, email, message).

Code

Editor
Loading...
Preview

Knowledge Check

Test your understanding of controlled components and form handling.

In the controlled component pattern for a text input in React, what is responsible for updating the input's visible value?

  • Select an answer to view feedback.

Why is calling 'event.preventDefault()' typically necessary inside a form's 'onSubmit' handler in a React single-page application?

  • Select an answer to view feedback.

When using a single state object to manage multiple form inputs with a generic 'handleChange' function, what HTML attribute is crucial for identifying which input triggered the change?

  • Select an answer to view feedback.

Summary

This lesson introduced the fundamental concept of controlled components for handling forms in React. We learned that by linking input values to component state (value prop) and updating that state via onChange handlers, React gains full control over the form’s data. We explored how to implement this pattern for various input types (text, textarea, select, checkbox) and how to handle form submissions using onSubmit while preventing the default browser behavior. We also discussed strategies for managing state for multiple inputs using either individual useState calls or a single state object. Mastering forms is essential for building interactive user interfaces.


References

Additional Resources

  • React Hook Form - Popular form library with built-in validation and performance optimization
  • Formik - Form library that helps with form state management and validation