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.
To keep track of each input’s value, you need:
State for the input that will manage the input’s value
A value
attribute on the input that corresponds to that piece of state
An onChange
listener attached to the input to monitor users behavior and update state as the user interacts with the field
Form components also need an onSubmit
listener on the form element to handle the submitted form data.
// so don't do this because it won't update the DOM
state.prop = "New Value"
setState(state);
// do this instead because it will update the DOM
// because React will see the state is set to a new object
setState({...state, prop: "New Value"})
const [name, setName] = useState("");
const [about, setAbout] = useState("");
const [phase, setPhase] = useState("");
const [link, setLink] = useState("");
const [image, setImage] = useState("");
A more elegant approach is to create a state object with key/value pairs associated with each form field:
const [formData, setFormData] = useState({
name: "",
about: "",
phase: "",
link: "",
image: "",
});
Note: This approach works well for a form that has multiple string, number, textarea, & select inputs but gets a bit clunkier when the form includes inputs like checkboxes or files. React docs recommend an external library like Formik as a complete solution for forms.
React Hook Form is another approach to handling forms in React that has become popular as well.
2. Connect the value
attribute of each input field to the corresponding state variable:
Example:
<input
type="text"
id="name"
value={formData.name}
/>
❗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.
3. Add an onChange listener for each input field using a helper function: Example:
<input
type="text"
id="name"
value={formData.name}
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={handleAboutChange} />
<input type="text" id="phase" onChange={handlePhaseChange} />
4. Adding a name
attribute to the input fields:
<input
type="text"
id="link"
onChange={handleOnChange}
value={formData.link}
name="link"
/>
❗ 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 ‘link’ then the name
attribute for the corresponding input field should be link
as well
5. 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
const { name, value } = e.target;
// This is the same as doing:
// const name = e.target.name
// const value = e.target.value
// 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 }));
};
6. 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();
};
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.
💡 What do we need to do?
App
component called onAddProject
that will update the projects
state:const onAddProject = (newProject) => {
setProjects(projects => [...projects, newProject]);
};
<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: "",
});
};