Lab 15: Form Values to State
Objectives
- Add form data to component state
- Make form fields controlled components
- Handle submission of the form
Steps
Add form data to component state
Open the file
src\projects\ProjectForm.js
.To the
propTypes
, add aproject
prop.src\projects\ProjectForm.js
...
ProjectForm.propTypes = {
+ project: PropTypes.instanceOf(Project),
onSave: PropTypes.func.isRequired,
onCancel: PropTypes.func.isRequired
};
...Destructure the
project
prop in the function component signature and rename itinitialProject
so that we can name our state variableproject
. Next, create a state variableproject
using theuseState
hook.src\projects\ProjectForm.js
- import React from 'react';
+ import React, { useState } from 'react';
function ProjectForm({
+ project: initialProject,
onSave,
onCancel,
}) {
+ const [project, setProject] = useState(initialProject);
const handleSubmit = (event) => {
event.preventDefault();
onSave(new Project({ name: 'Updated Project' }));
};
...
}
Make form fields controlled components
Make all
<input />
s and<textarea />
s controlled components by assigning their values to aproject
property onstate
.Write a
handleChange
event handler and wire it up toonChange
event of all the form fields.The form field types that need to be handled include:
<input type="text" />
<input type="number" />
<input type="checkbox" />
<textarea />
Alternatively, you could write a separate handler for each of the form field types and invoke them as appropriate but this can be tedious and more difficult to maintain.
src\projects\ProjectForm.js
...
function ProjectForm({
project: initialProject,
onSave,
onCancel,
}) {
const [project, setProject] = useState(initialProject);
const handleSubmit = (event) => {
event.preventDefault();
onSave(new Project({ name: 'Updated Project' }));
};
+ const handleChange = (event) => {
+ const { type, name, value, checked } = event.target;
+ // if input type is checkbox use checked
+ // otherwise it's type is text, number etc. so use value
+ let updatedValue = type === 'checkbox' ? checked : value;
+
+ //if input type is number convert the updatedValue string to a +number
+ if (type === 'number') {
+ updatedValue = Number(updatedValue);
+ }
+ const change = {
+ [name]: updatedValue,
+ };
+
+ let updatedProject;
+ // need to do functional update b/c
+ // the new project state is based on the previous project state
+ // so we can keep the project properties that aren't being edited +like project.id
+ // the spread operator (...) is used to
+ // spread the previous project properties and the new change
+ setProject((p) => {
+ updatedProject = new Project({ ...p, ...change });
+ return updatedProject;
+ });
+ };
return (
<form className="input-group vertical" onSubmit={handleSubmit}>
<label htmlFor="name">Project Name</label>
<input
type="text"
name="name"
placeholder="enter name"
+ value={project.name}
+ onChange={handleChange}
/>
<label htmlFor="description">Project Description</label>
<textarea
name="description"
placeholder="enter description"
+ value={project.description}
+ onChange={handleChange}
/>
<label htmlFor="budget">Project Budget</label>
<input
type="number"
name="budget"
placeholder="enter budget"
+ value={project.budget}
+ onChange={handleChange}
/>
<label htmlFor="isActive">Active?</label>
<input
type="checkbox"
name="isActive"
+ checked={project.isActive}
+ onChange={handleChange}
/>
<div className="input-group">
<button className="primary bordered medium">Save</button>
<span />
<button type="button" className="bordered medium" onClick={onCancel}>
cancel
</button>
</div>
</form>
);
}
export default ProjectForm;
Handle submission of the form
In
handleSubmit
, when calling theonSave
prop
passstate.project
instead ofnew Project({ name: 'Updated Project' })
.src\projects\ProjectForm.js
...
function ProjectForm({
project: initialProject,
onSave,
onCancel,
}) {
const [project, setProject] = useState(initialProject);
const handleSubmit = (event) => {
event.preventDefault();
- onSave(new Project({ name: 'Updated Project' }));
+ onSave(project);
};
...
}
export default ProjectForm;
In
ProjectList
set theproject
prop into the<ProjectForm />
.src\projects\ProjecList.js
...
function ProjectList({ projects, onSave }) {
const [projectBeingEdited, setProjectBeingEdited] = useState({});
const handleEdit = (project) => {
setProjectBeingEdited(project);
};
const cancelEditing = () => {
setProjectBeingEdited({});
};
return (
<div className="row">
{projects.map((project) => (
<div key={project.id} className="cols-sm">
{project === projectBeingEdited ? (
<ProjectForm
+ project={project}
onSave={onSave}
onCancel={cancelEditing}
/>
) : (
<ProjectCard project={project} onEdit={handleEdit} />
)}
</div>
))}
</div>
);
}
export default ProjectList;ProjectsPage update the project.
src\projects\ProjectsPage.js
import React, { Fragment,
+ useState } from 'react';
import { MOCK_PROJECTS } from './MockProjects';
import ProjectList from './ProjectList';
function ProjectsPage() {
+ const [projects, setProjects] = useState(MOCK_PROJECTS);
const saveProject = (project) => {
- console.log('Saving project: ', project);
+ let updatedProjects = projects.map((p) => {
+ return p.id === project.id ? project : p;
+ });
+ setProjects(updatedProjects);
};
return (
<Fragment>
<h1>Projects</h1>
- <ProjectList onSave={saveProject} projects={MOCK_PROJECTS} />
+ <ProjectList onSave={saveProject} projects={projects} />
</Fragment>
);
}
export default ProjectsPage;Verify the application is working by following these steps in your browser.
- Click the edit button for a project.
- Change the project name in the form.
- Click save on the form.
- Verify the card shows the updated data.
Note that if you refresh your browser page your changes will not persist because the updates are only happening in the browser's memory. We will get a more permanent save working in a future lab when we communicate to our backend web API.