Differences Between HTML Form

  • The for attribute in HTML form is replaced by htmlFor in React
    • <label htmlFor="nameId">Name:</label>

Controlled vs Uncontrolled Elements

A controlled input is one where the <input>, <select>, or<textarea> element sets the value attribute/prop. Then React expects the app to update that value via onChange

  • Nothing will show up even if the user tries typing in input
  • Requires app to handle onChange event to update the value
  • If the input type is file, then an uncontrolled element is required

An uncontrolled input is when the value attribute/prop is not set

// Controlled input
function Input() {
  const [query, setQuery] = useState('');
  function handleChange(event) {
    setQuery(() => event.target.value);
  }

  return (
    <input
      type="text"
      value={query}    // controlled
      onChange={handleChange} // needed to update state
      placeholder="Search..."
    />
  );
}
// Uncontrolled input
function Input() {
  return <input type="text" placeholder="Search..." />;
}

Common elements that can be controlled

They will need an onChange event handler to update the prop

ElementControlled PropNotes
<input type="text" />valueStandard text input.
<input type="password" />valueSame as text input.
<input type="number" />valueValue is a string; convert to number if needed.
<input type="email" />valueSame as text input.
<input type="checkbox" />checkede.target.checked gives boolean value.
<input type="radio" />checkedUsually grouped by name; e.target.value gives selected value.
<textarea>valueMulti-line text input.
<select>valueSingle select. e.target.value gives selected option.
<select multiple>valueMulti-select. Use Array.from(e.target.selectedOptions, o => o.value) to get array of selected values.
Custom input componentsDepends on prop (value or checked)Pass value/checked from parent and trigger handler on change.

Uncontrolled Sample for Form

  • The<input> does not its value attribute set
  • Use useRef hook to get the value of the form once submit is pressed.
  • Use formRef.current.reset() to reset the whole form.
  • Easy to understand for small forms
import React, { useRef } from 'react';

export default function UncontrolledForm() {
  const nameRef = useRef(null);
  const colorRef = useRef(null);
  const formRef = useRef(null);

  // Handle form submission
  const handleSubmit = (e) => {
    e.preventDefault();
    const name = nameRef.current.value;
    const color = colorRef.current.value;
    alert(`Submitted:\nName: ${name}\nFavorite Color: ${color}`);
  };

  // Handle form reset
  const handleReset = () => {
    formRef.current.reset();
  };

  return (
    <form ref={formRef} onSubmit={handleSubmit}>

      {/* Input Text */}
      <div>
        <label htmlFor="name">Name:</label>
        <input
          id="name"
          name='name'
          type="text"
          ref={nameRef}
          placeholder="Enter your name"
        />
      </div>

      {/* Select Dropdown */}
      <div>
        <label htmlFor="color">Favorite Color:</label>
        <select
          id="color"
          name='color'
          ref={colorRef}
        >
          <option value=''>-- Select --</option>
          <option value="Red">Red</option>
          <option value="Blue">Blue</option>
          <option value="Green">Green</option>
        </select>
      </div>

      {/* Buttons */}
      <div>
        <button type="button" onClick={handleReset}>Reset</button>
        <button type="submit">Submit</button>
      </div>
    </form>
  );
}

Controlled Sample for Form

  • More scalable than uncontrolled version
  • Use useState hook to keep track of the value of the fields.
  • Use setState() to reset the whole form at once
  • Good for large forms
  • Good for forms that need validation on certain fields
import React, { useState } from 'react';

export default function App() {
  const [formData, setFormData] = useState({});
  const [status, setStatus] = useState('');

  const handleChange = ({ target }) => {
    const { name, value } = target;
    setFormData((prev) => ({
      ...prev,
      [name]: value, // dynamic field update
    }));
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    setStatus('Submitting...');
    alert(JSON.stringify(formData, '', 2));
  };

  const handleReset = () => {
    setFormData({});
  };

  return (
    <>
      <form onSubmit={handleSubmit}>

        {/* Input Text */}
        <div>
          <label htmlFor="name">Name:</label>
          <input
            id="name"
            name="name"
            type="text"
            value={formData.name}
            onChange={handleChange}
            placeholder="Enter your name"
            required
          />
        </div>

        {/* Select Dropdown */}
        <div>
          <label htmlFor="color">Favorite Color:</label>
          <select
            id="color"
            name="color"
            value={formData.color}
            onChange={handleChange}
            required
          >
            <option value="">-- Select --</option>
            <option value="Red">Red</option>
            <option value="Blue">Blue</option>
            <option value="Green">Green</option>
          </select>
        </div>

        {/* Buttons */}
        <div>
          <button type="button" onClick={handleReset}>Reset</button>
          <button type="submit">Submit</button>
        </div>
      </form>

      {status && <p>{status}</p>} // If status is true, render <p>{status}</p>
    </>
  );
}

Submitting Form

Use the onSubmit attribute on the <form> element to give the callback function

  • Use the Fetch API with POST method: Fetch API notes
  • Zybooks is a good place to test form by sending the form to this link: https://wp.zybooks.com/form-viewer.php
    • To test the form, set its attribute action="https://wp.zybooks.com/form-viewer.php"
    • It will be possible to view what data the server sees
const handleSubmit = async (event) => {
  event.preventDefault();   // prevent browser from refreshing or navigating away on submit
  setStatus('Submitting...');

  try {
    const response = await fetch('https://wp.zybooks.com/form-viewer.php', {
      method: 'POST',
      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
      body: new URLSearchParams(formData),
    });

    if (response.ok) {
      setStatus('Form submitted successfully!');
      handleReset(); // reset state
    } else {
      setStatus('Submission failed. Please try again.');
    }
  } catch (error) {
    setStatus('Network error. Please try later.');
  }
};