ReviseAlgo Logo

Fundamentals

Forms

Master React form handling, covering controlled vs uncontrolled inputs, submission validation, and state synchronization loops.

Last Updated: July 15, 2026 12 min read

1. Learning Objectives

In this lesson, you will learn how to capture and validate user inputs using forms in React. By the end of this topic, you will be able to:

  • Differentiate between Controlled and Uncontrolled form components.
  • Synchronize input fields with local state using value and onChange.
  • Handle form submission events and prevent default browser page reloads.
  • Implement form field validation and display inline validation errors.
  • Manage complex forms containing multiple input fields.

2. Overview

Handling forms in React differs from vanilla HTML. The recommended pattern is using Controlled Components, where the form's input values are driven by React state. This ensures that the React state acts as the "single source of truth" for the form data, making it easy to validate and submit values.

3. Why This Topic Matters

Forms are the primary way users submit data to web applications:

  • Real-Time Feedback: Controlled inputs allow you to validate user data as they type (e.g. displaying a warning if an email address is invalid before they hit submit).
  • State Synchronization: Syncing inputs with React state makes it easy to enable or disable submit buttons, implement autocomplete dropdowns, and pre-populate fields.

4. Real-World Analogy

Think of controlled inputs like a bank transaction log:

  • Uncontrolled Input (Cash transaction): You hand money directly to a cashier. The register updates, but you don't have a synchronized ledger tracking it automatically. The DOM holds the value, and you have to query it manually to read it.
  • Controlled Input (Digital ledger): You check your bank app before and after a transaction. Every transaction updates the bank's database (state), which then updates your app display (UI) in real-time. The state is the source of truth, and the input field simply displays the current state.

5. Core Concepts

Below is a comparison of Controlled and Uncontrolled components in React:

Feature Controlled Components (Recommended) Uncontrolled Components
Source of Truth React state. The browser DOM.
Data Access Read directly from state variables. Accessed via DOM references (e.g. useRef).
Real-Time Validation Easy; validation runs on every keystroke. Difficult; require querying the DOM elements.
Value Binding Uses value={{{state}}} and onChange={{{handler}}}. Uses defaultValue or no binding.

6. Syntax & API Reference

This example shows a standard controlled form in React:

7. Visual Diagram

This diagram displays the synchronization loop inside a controlled input:

8. Live Example — Full Working Code

Below is a complete HTML page demonstrating a signup form with real-time validation:

What just happened? Input fields are bound to the username and password states. Typing in these inputs updates the state, which then updates the displayed values. When the form is submitted, the submit handler validates the values, displays errors if they are too short, or displays a success message.

9. Interactive Playground

Try It Yourself Challenges:

  1. Add a checkbox for "Accept Terms of Service" and disable the submit button if the checkbox is unchecked.
  2. Validate that the username does not contain spaces when the form is submitted.

10. Common Mistakes

Mistake Why it happens Wrong Correct
Binding input value without an onChange handler If you bind the value prop to a state variable without an onChange handler, the input becomes read-only and the user cannot type in it. <input value={val} /> <input value={val} onChange={(e) => setVal(e.target.value)} />
Omitting preventDefault on form submit Omitting event.preventDefault() allows the browser to reload the page on form submission, losing all local React state. const handleSubmit = () => {
saveData();
};
const handleSubmit = (e) => {
e.preventDefault();
saveData();
};

11. Best Practices

  • Use controlled components: Bind input values to React state for form inputs.
  • Attach onSubmit to the form tag: Trigger form submission via the tag's onSubmit event, rather than attaching onClick listeners to the submit button.
  • Keep HTML accessibility tags: Use proper form elements (like ) to keep forms accessible.
  • Reset forms on success: Clear state values after successful submissions to reset form fields.

12. Browser Compatibility/Requirements

React form elements run consistently across all modern web browsers.

13. Interview Questions

Q1: What is the difference between controlled and uncontrolled components in React?

Answer:

  • Controlled: The form input's value is driven by React state, which acts as the single source of truth. Changes to the input trigger state updates, which then update the displayed value.
  • Uncontrolled: The input's value is managed by the browser DOM. The component reads values from the DOM when needed (like on form submission) using refs (e.g. useRef).

Q2: Why is calling event.preventDefault() important in form submit handlers?

Answer: By default, browsers trigger a full page reload when a form is submitted. Calling event.preventDefault() stops this default behavior. This allows JavaScript to handle form validation and submit data asynchronously (via APIs) without losing the application's local state.

14. Debugging Exercise

Identify why typing in this input field has no effect and the field remains empty:

function CommentBox() {
  const [comment, setComment] = useState('');

return ( setComment(comment)} // Incorrect update parameter /> ); }</code></pre> <details class="text-sm text-gray-700 dark:text-gray-300 mt-3"> <summary class="cursor-pointer font-bold text-blue-600 dark:text-blue-400">View Solution</summary> <div class="mt-2 space-y-2"> <p><strong>Diagnosis:</strong> The <code>onChange</code> handler is updating the state to the current <code>comment</code> value (which is empty), rather than reading the new input value from the event object.</p> <p><strong>Fix:</strong> Read the new value from <code>event.target.value</code>:</p> <pre class="bg-gray-955 p-2 rounded text-xs text-orange-400"><code>onChange={(e) => setComment(e.target.value)}</code></pre> </div> </details> </div></p> <h2 class="text-2xl font-bold mt-8 mb-4 border-b border-gray-200 dark:border-gray-800 pb-2 text-gray-900 dark:text-white">15. Practice Exercises</h2> <div class="space-y-4"> <div> <p class="font-semibold text-gray-900 dark:text-white">Exercise 1: Create a password confirmation form</p> <p class="text-sm text-gray-700 dark:text-gray-300"> Build a form containing "Password" and "Confirm Password" inputs. Display a red validation warning if the two values do not match during typing or form submission. </p> </div> </div> <h2 class="text-2xl font-bold mt-8 mb-4 border-b border-gray-200 dark:border-gray-800 pb-2 text-gray-900 dark:text-white">16. Scenario-Based Challenge</h2> <div class="p-4 bg-gray-50 dark:bg-gray-900 border border-gray-200 dark:border-gray-800 rounded-xl mb-6"> <p class="font-bold text-lg mb-2">The Dynamic Checkout Form Address Field Autocomplete:</p> <p class="text-sm mb-3"> You are building an address input form. When the user types, the app queries an autocomplete API and displays a list of matching addresses. Clicking a suggestion should populate the form fields. Describe how you would implement this using controlled inputs and event listeners. </p> </div> <h2 class="text-2xl font-bold mt-8 mb-4 border-b border-gray-200 dark:border-gray-800 pb-2 text-gray-900 dark:text-white">17. Quick Quiz</h2> <div class="space-y-6"> <div> <p class="font-semibold text-gray-900 dark:text-white">Q1: In a controlled component, what is the source of truth for the input's value?</p> <p class="text-sm">A) The browser DOM.</p> <p class="text-sm">B) React state.</p> <p class="text-sm">C) The value attribute string.</p> <p class="text-xs font-bold text-red-600 dark:text-red-400 mt-1">Answer: B — In a controlled component, React state serves as the single source of truth for the input's value.</p> </div> </div> <h2 class="text-2xl font-bold mt-8 mb-4 border-b border-gray-200 dark:border-gray-800 pb-2 text-gray-900 dark:text-white">18. Summary & Key Takeaways</h2> <ul class="mb-6 space-y-2 pl-5 list-disc text-gray-700 dark:text-gray-300"> <li>In controlled components, input values are bound to and managed by React state.</li> <li>In uncontrolled components, input values are managed by the browser DOM and accessed via refs.</li> <li>Call <code>event.preventDefault()</code> inside form submissions to prevent full page reloads.</li> <li>Controlled components simplify form validation and enable real-time UI feedback.</li> </ul> <h2 class="text-2xl font-bold mt-8 mb-4 border-b border-gray-200 dark:border-gray-800 pb-2 text-gray-900 dark:text-white">19. Cheat Sheet</h2> <div class="overflow-x-auto mb-6"> <table class="min-w-full divide-y divide-gray-200 dark:divide-gray-800 text-sm"> <thead> <tr class="bg-gray-50 dark:bg-gray-900 text-gray-700 dark:text-gray-300"> <th class="px-4 py-2 text-left font-semibold">Form Pattern</th> <th class="px-4 py-2 text-left font-semibold">Purpose</th> </tr> </thead> <tbody class="divide-y divide-gray-200 dark:divide-gray-800 text-gray-700 dark:text-gray-300"> <tr> <td class="px-4 py-2 font-mono"><code>value={{val}}</code></td> <td class="px-4 py-2">Binds input display to a state variable.</td> </tr> <tr> <td class="px-4 py-2 font-mono"><code>onChange={(e) => setVal(e.target.value)}</code></td> <td class="px-4 py-2">Syncs input changes with the state variable.</td> </tr> <tr> <td class="px-4 py-2 font-mono"><code>event.preventDefault()</code></td> <td class="px-4 py-2">Prevents the default browser behavior on form submission.</td> </tr> </tbody> </table> </div>