Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 31

Handling Form Submissions with `onSubmit`

Recommended resources

Hello! Welcome back.

In our last lesson, we mastered controlling various complex form elements like dropdowns, checkboxes, and radio buttons, successfully gathering user input into our component's state. We now have all the data we need. The question is, what's next? How do we actually do something with it?

Today, we'll answer that question by focusing on the final, crucial step of form handling. You will learn to manage form submission using the onSubmit event handler. This involves capturing the submit event, preventing the default browser behavior, and processing the collected state data.


1. The onSubmit Event Handler

In a traditional HTML form, you might think about handling the click event on the submit button. However, React encourages a more robust approach: listening for the submit event on the <form> element itself. This is better because it also captures submissions triggered by pressing the "Enter" key in an input field, which is standard user behavior.

The onSubmit event handler is attached directly to the <form> tag.

React onSubmit Event Handler Demonstration
This GIF demonstrates the core flow: a user interacts with a form, a submit event is triggered, and a function is executed to process the input.

The basic structure looks like this:

function MyForm() {
  const handleSubmit = (event) => {
    // ... logic to handle the submission
  };

  return (
    <form onSubmit={handleSubmit}>
      {/* ...your form inputs... */}
      <button type="submit">Submit</button>
    </form>
  );
}

Notice we are passing the handleSubmit function reference, not calling it (handleSubmit() would be incorrect). React will invoke this function for us when the form is submitted.



2. Preventing the Default Behavior

If you were to implement the code above, you'd notice something strange: the page reloads the moment you submit the form. This is the default browser behavior for form submissions. In a single-page application (SPA) like the ones we build with React, a full-page reload is undesirable as it wipes out all client-side state.

To prevent this, we need to call the preventDefault() method on the event object that is automatically passed to our handler.

This short video from The Net Ninja clearly demonstrates how to attach the onSubmit handler and use event.preventDefault().

Full React Tutorial #28 - Submit Events

Watch the first two parts of this video to see the onSubmit handler in action and learn why event.preventDefault() is essential.

Watch from the beginning until 01:30. Pay attention to: How the onSubmit handler is attached to the <form> tag. The syntax of the handler function, which automatically receives the event object. The use of event.preventDefault() to stop the page from refreshing.

The React official documentation also provides a concise explanation of this concept.

Responding to Events - React

This section from the official React documentation, 'Responding to Events', provides a clear example and explanation for preventing default browser behavior.

Please read the section titled 'Preventing default behavior'. It reinforces what you just saw in the video and explains the purpose of e.preventDefault() succinctly.

The key takeaway is that the very first line inside your submission handler should almost always be event.preventDefault();.



3. Processing and Resetting the Form

Now that we've stopped the page from reloading, our handleSubmit function becomes the perfect place to work with our form data. The typical workflow inside this function is:

  1. Call event.preventDefault().
  2. (Optional but recommended) Validate the data. We'll cover this in our next lesson.
  3. Access the state variables to gather the form data into a structured object.
  4. Use the data (e.g., send it to an API, pass it to a parent component).
  5. Reset the form fields to their initial state for a better user experience.

Let's see how to gather the data and then explore a complete example that includes resetting the form.

Full React Tutorial #28 - Submit Events

Continuing with the Net Ninja video, this next part shows how to take the data from your state variables and assemble it into an object, ready for use.

Watch from 01:30 to 02:31. The key idea here is creating a new blog object using the values stored in the component's state (title, body, author).

This next resource provides a complete, runnable example and a great explanation for why we need preventDefault() in modern web applications.

What is preventDefault() in React

This article by Robin Wieruch, 'What is preventDefault() in React', ties everything together with a practical example of a to-do list.

Please review the code example and read the section 'Why is a form submit reloading the browser?'. Focus on: The handleSubmit function: See how it calls preventDefault(), adds the new item to the list (setList), and then clears the input field (setValue('')). The explanation: Understand that preventDefault() allows React to take control of the submission process, enabling us to manage state ourselves instead of letting the browser do a full refresh.

As you've seen, the handleSubmit function is the control center for your form's data. It's where you decide what happens after the user clicks "Submit".



Practice Exercise

Let's apply what you've learned by enhancing the PreferencesForm component from our last lesson. Your task is to implement a fully functional submission process, including resetting the form.

Goal: Modify the handleSubmit function to log the collected data and reset the form to its initial state.

Starter Code:
This is the solution from our previous lesson. Your work will be inside the handleSubmit function.

import { useState } from 'react';

const interestsData = ['AI', 'Coding', 'Music', 'Gaming', 'Audio'];

const initialInterestsState = interestsData.reduce((acc, interest) => {
  acc[interest] = false;
  return acc;
}, {});

// Define the initial state outside the component so we can reuse it for resetting
const initialPreferences = {
  theme: 'light',
  notificationFrequency: 'weekly',
  interests: initialInterestsState,
};

function PreferencesForm() {
  const [preferences, setPreferences] = useState(initialPreferences);

  const handleChange = (e) => {
    const { name, value, type, checked } = e.target;
    if (type === 'checkbox') {
      setPreferences(prev => ({
        ...prev,
        interests: {
          ...prev.interests,
          [name]: checked,
        },
      }));
    } else {
      setPreferences(prev => ({
        ...prev,
        [name]: value,
      }));
    }
  };

  const handleSubmit = (e) => {
    // 1. Prevent the default form submission behavior.
    
    // 2. Log the current `preferences` state object to the console
    //    with a descriptive message, e.g., "Form Submitted with:"
    console.log('Submitting Preferences:', preferences);

    // 3. Reset the form by setting the state back to `initialPreferences`.
    
    // 4. (Optional) Add an alert to notify the user that their
    //    preferences have been saved.
  };

  return (
    <form onSubmit={handleSubmit}>
      <h3>User Preferences</h3>
      {/* The rest of the form JSX remains the same... */}
      <fieldset>
        <legend>Theme</legend>
        <label>
          <input
            type="radio"
            name="theme"
            value="light"
            checked={preferences.theme === 'light'}
            onChange={handleChange}
          />
          Light
        </label>
        <label>
          <input
            type="radio"
            name="theme"
            value="dark"
            checked={preferences.theme === 'dark'}
            onChange={handleChange}
          />
          Dark
        </label>
      </fieldset>
      <label>
        Notification Frequency:
        <select
          name="notificationFrequency"
          value={preferences.notificationFrequency}
          onChange={handleChange}
        >
          <option value="none">None</option>
          <option value="daily">Daily</option>
          <option value="weekly">Weekly</option>
        </select>
      </label>
      <fieldset>
        <legend>Interests</legend>
        {interestsData.map((interest) => (
          <label key={interest}>
            <input
              type="checkbox"
              name={interest}
              checked={preferences.interests[interest]}
              onChange={handleChange}
            />
            {interest}
          </label>
        ))}
      </fieldset>
      <button type="submit">Save Preferences</button>
    </form>
  );
}

export default PreferencesForm;
Click to see a possible solution
// ... (imports and initial state setup are the same)

const initialPreferences = {
  theme: 'light',
  notificationFrequency: 'weekly',
  interests: interestsData.reduce((acc, interest) => ({ ...acc, [interest]: false }), {}),
};

function PreferencesForm() {
  const [preferences, setPreferences] = useState(initialPreferences);

  // ... (handleChange is the same)
  const handleChange = (e) => {
    const { name, value, type, checked } = e.target;
    if (type === 'checkbox') {
      setPreferences(prev => ({
        ...prev,
        interests: {
          ...prev.interests,
          [name]: checked,
        },
      }));
    } else {
      setPreferences(prev => ({
        ...prev,
        [name]: value,
      }));
    }
  };

  const handleSubmit = (e) => {
    // 1. Prevent the default form submission behavior.
    e.preventDefault();
    
    // 2. Log the current `preferences` state object.
    console.log('Form Submitted with:', preferences);

    // 4. (Optional) Add an alert.
    alert('Preferences saved successfully!');

    // 3. Reset the form by setting the state back to `initialPreferences`.
    setPreferences(initialPreferences);
  };

  // ... (return with JSX is the same)
  return (
    <form onSubmit={handleSubmit}>
      <h3>User Preferences</h3>
      <fieldset>
        <legend>Theme</legend>
        <label>
          <input
            type="radio"
            name="theme"
            value="light"
            checked={preferences.theme === 'light'}
            onChange={handleChange}
          />
          Light
        </label>
        <label>
          <input
            type="radio"
            name="theme"
            value="dark"
            checked={preferences.theme === 'dark'}
            onChange={handleChange}
          />
          Dark
        </label>
      </fieldset>
      <label>
        Notification Frequency:
        <select
          name="notificationFrequency"
          value={preferences.notificationFrequency}
          onChange={handleChange}
        >
          <option value="none">None</option>
          <option value="daily">Daily</option>
          <option value="weekly">Weekly</option>
        </select>
      </label>
      <fieldset>
        <legend>Interests</legend>
        {interestsData.map((interest) => (
          <label key={interest}>
            <input
              type="checkbox"
              name={interest}
              checked={preferences.interests[interest]}
              onChange={handleChange}
            />
            {interest}
          </label>
        ))}
      </fieldset>
      <button type="submit">Save Preferences</button>
    </form>
  );
}

export default PreferencesForm;


Conclusion

Congratulations! You have now learned the complete lifecycle of a form in React: from capturing user input with controlled components to processing that data upon submission.

Key Takeaways:

  • Handle form submissions with the onSubmit event handler on the <form> element, not the onClick handler on the button.
  • Always call event.preventDefault() as the first step in your submit handler to prevent a full page reload.
  • The handleSubmit function is the ideal place to access your form's state, process the data, and trigger further actions.
  • Resetting the form's state after a successful submission is a key part of good user experience.

Next Up:

Our forms are functional, but they aren't very user-friendly if the input is incorrect. In our next lesson, we will address this by learning how to implement client-side form validation and display error messages conditionally. This will make our forms more robust and interactive.