React Forms 📝

✅ Objectives

  • Explain why we use controlled forms (vs uncontrolled forms)

  • Implement a controlled form

  • Use form data to update state in a parent component

What is a controlled input

In React, rather than looking into the DOM to get the form’s input field values when the form is submitted, we use state to monitor the user’s input as they type, so that our component state is always in sync with the DOM.

controlled input diagram

Uncontrolled Form - vanilla JS

<body>
  <form id="uncontrolledForm">
    <input type="text" id="nameInput" placeholder="Enter your name" />
    <button type="submit">Submit</button>
  </form>

  <script>
    document.getElementById('uncontrolledForm').addEventListener('submit', function(event) {
      event.preventDefault();
      const name = document.getElementById('nameInput').value;
      alert(`Hello, ${name}!`);
    });
  </script>

Controlled Form - ReactJS

function ControlledFormFunctional() {
  const [name, setName] = useState('');

  const handleInputChange = (event) => {
    setName(event.target.value);
  };

  const handleSubmit = (event) => {
    event.preventDefault();
    alert(`Hello, ${name}!`)};
  return (
    <form onSubmit={handleSubmit}>
      <input
        value={name}
        onChange={handleInputChange}
      />
      <button type="submit">Submit</button>
    </form>
  )}

Uncontrolled Form VS Controlled Form

Uncontrolled Form using Vanilla JS:

  • The form data is managed by the DOM itself.
  • We directly access DOM elements to retrieve the values when needed.

Controlled Form using React:

  • The form data is managed by the React component’s state.
  • The form fields are bound to the state, and their values are updated via event handlers.

Making an input controlled

To keep track of each input’s value, you need:

  1. State for the input that will manage the input’s value

  2. An onChange listener attached to the input to monitor users behavior and update state as the user interacts with the field

  3. A value attribute on the input that corresponds to a key in state

And for the form itself, you need an onSubmit listener on the form to finally submit data.

🛠️ ProjectForm setup

  1. For each input element in the form, create a new state variable:
const [name, setName] = useState("");
const [about, setAbout] = useState("");
const [phase, setPhase] = useState("");
const [link, setLink] = useState("");
const [image, setImage] = useState("");

❗ Most common approach (and cleanest) is to create a state object with key/value pairs associated with each form field:

const [formData, setFormData] = useState({
  name: "",
  about: "",
  phase: "",
  link: "",
  image: "",
});
  1. Add an onChange handler for each input field using a helper function:

Example:

<input type="text" id="about" onChange={handleOnChange} />

🤯 If using individual pieces of state for form fields, a separate helper function will be created for each corresponding field.

Example:

<input type="text" id="about" onChange={handleAbout} />
<input type="text" id="phase" onChange={handlePhase} />
  1. Connect the value attribute of each input field to the corresponding state variable:

Example:

<input
  type="text"
  id="about"
  onChange={handleOnChange}
  value={formData.about}
/>

Note The reason formData.name is being used is because the state variable is an object named formData. To access the value of a key within the object, dot notation is used.

  1. Adding a name attribute to the input fields:
<input
  type="text"
  id="about"
  onChange={handleOnChange}
  value={formData.about}
  name="about"
/>

IMPORTANT: The name attribute needs to match with the key created in the state object in order to update the value. If the key in the state object is ‘about’ then the name attribute for the corresponding input field should be about as well

  1. Updating the state when the onChange occurs (aka when the user begins typing or changing parts of the form):
const handleOnChange = (e) => {
  // e.target will return an object, the element that triggered the event with properties
  // including name and value. Object destructuring is used to extract that values from e.target

  // This is the same as doing:
  // const name = e.target.name
  // const value = e.target.value

  const { name, value } = e.target;

  // The setter function is then invoked and a new object will  be created with the 
  // contents of the previous formData spread and the new key/value added to avoid overwriting the 
  // previous form field values

  setFormData((formData) => ({ ...formData, [name]: value }));
};
  1. On the <form> element, add an onSubmit listener with a handleSubmit helper function that will run when the form is submitted:
<form className="form" autoComplete="off" onSubmit={handleSubmit}></form>
const handleSubmit = (e) => {
  e.preventDefault();
};

🔑 After the form has been submitted

The state of projects is defined inside of the parent component App and the behavior occurs in the child component ProjectForm. When the new project is submitted, projects will need to be updated to include it.

Here is where the process of inverse data flow will need to occur:

  1. Create a helper function in App component called onAddProject that will update the projects state:
const onAddProject = (newProject) => {
  setProjects([...projects, newProject]);
};

projects is an array so to update the state, a new array will be created with the elements of the original projects array spread and the new project passed to onAddProject added as a new element

Pass onAddProject as a prop to ProjectFrom from within App component:

<ProjectForm onAddProject={onAddProject} />

Inside the ProjectForm component, destructure onAddProject from the props and invoke it from within the handleSubmit function, passing it the formData object:

const handleSubmit = (e) => {
  e.preventDefault();
  onAddProject(formData);

  // after we have delivered the formData to the App component and updated state
  // clear the form by setting the values back to empty strings:

  setFormData({
    name: "",
    about: "",
    phase: "",
    link: "",
    image: "",
  });
};

💡 Conclusion

State is a very integral part of the way that React applications operate and DOM manipulation to occur. React prefers using state to update the forms and keep track of the form fields values, making them controlled inputs. What our user sees in the input fields reflects the value of the state associated with that field.