How to build a React CRUD todo app (refactor)
In this series, we have built a todo application.
I suggest following along and if you get stuck, you can fork the code from the Code Sandbox.
In the previous posts, added the ability to edit todos. We have full CRUD functionality, but the application could be structured better, lets work on that.
There are a couple benefits of breaking up components:
- Reusability - you are able to use a component anywhere in the application.
- Isolation - it's helps with isolating bugs found in your code.
This is known as abstraction
1. Refactor todo item into it's own component
Start by moving the todo item to it's own component.
- Create a new file called
TodoItem.js
. - Add the
li
item from theApp.js
file
We are going to change the names of props that are functions. It's standard convention to change the name for props that are functions to start with onSubjectEvent
. The TodoItem.js
should look like this.
// notice we are destructuring the props here. You could change this to
// bring in props instead and then just add `props.` to todo, onEditClick and onDeleteClick:
// export default function TodoItem(props)
export default function TodoItem({
// passing the todo as a prop
todo,
// notice the name change of the function handleEditClick to onEditClick
onEditClick,
// notice the name change of the function handleDeleteClick to onDeleteClick
onDeleteClick,
}) {
return (
// using the li element as the parent
<li key={todo.id}>
{todo.text}
{/* don't forget to change the name of the functions */}
<button onClick={() => onEditClick(todo)}>Edit</button>
<button onClick={() => onDeleteClick(todo.id)}>Delete</button>
</li>
);
}
- Import the
TodoItem
component into theApp.js
- Use the
TodoItem
and pass the required props to it
2. Refactor add todo form to it's own component
Start by moving the form for to add a new todo into a new file.
- Create a new file called
AddTodoForm.js
- Add the
form
element to add a new todo from theApp.js
file to our newAddTodoForm.js
file
Again, we are going to change the names of props that are functions. The AddTodoForm.js
should look like this.
export default function AddTodoForm({
// passing the todo as a prop
todo,
// notice the name change of the function handleAddFormSubmit to onAddFormSubmit
onAddFormSubmit,
// notice the name change of the function handleAddInputChange to onAddInputChange
onAddInputChange,
}) {
return (
// using the form element as the parent
// notice the change for the function names in the onSubmit and onChange props
<form onSubmit={onAddFormSubmit}>
<h2>Add Todo</h2>
<label htmlFor="todo">Create todo: </label>
<input
name="todo"
type="text"
placeholder="Create new todo"
value={todo}
onChange={onAddInputChange}
/>
</form>
);
}
- Import the
AddTodoForm
component into theApp.js
file - Use the
AddTodoForm.js
and pass the required props to it
3. Refactor add edit form to it's own component
Start by moving the form for to edit a todo into a new file.
- Create a new file called
EditForm.js
- Add the
form
element to edit a todo from theApp.js
file to our newEditForm.js
file
As a reminder, we are going to change the names of props that are functions. The EditForm.js
should look like this.
export default function EditForm({
// still need the currentTodo
currentTodo,
// also need to be able to toggle setIsEditing
setIsEditing,
// notice the name change of the function handleEditInputChange to onEditInputChange
onEditInputChange,
// notice the name change of the function handleEditFormSubmit to onEditFormSubmit
onEditFormSubmit,
}) {
return (
// using the form element as the parent
// notice the change for the function names in the onSubmit and onChange props
<form onSubmit={onEditFormSubmit}>
<h2>Edit Todo</h2>
<label htmlFor="updateTodo">Update todo: </label>
<input
name="updateTodo"
type="text"
placeholder="Update todo"
value={currentTodo.text}
onChange={onEditInputChange}
/>
<button type="submit" onClick={onEditFormSubmit}>
Update
</button>
<button onClick={() => setIsEditing(false)}>Cancel</button>
</form>
);
}
- Import
EditForm.js
component into theApp.js
file - Use the
EditForm.js
and pass the required props to it
4. Put it all together
After making the changes, we should have a file structure like this
App.js
file:
// App.js file
import { useEffect, useState } from "react";
import TodoItem from "./TodoItem";
import AddTodoForm from "./AddTodoForm";
import EditForm from "./EditForm";
import "./styles.css";
export default function App() {
import { useEffect, useState } from "react";
import TodoItem from "./TodoItem";
import AddTodoForm from "./AddTodoForm";
import EditForm from "./EditForm";
import "./styles.css";
export default function App() {
const [todos, setTodos] = useState(() => {
const savedTodos = localStorage.getItem("todos");
if (savedTodos) {
return JSON.parse(savedTodos);
} else {
return [];
}
});
const [todo, setTodo] = useState("");
const [isEditing, setIsEditing] = useState(false);
const [currentTodo, setCurrentTodo] = useState({});
useEffect(() => {
localStorage.setItem("todos", JSON.stringify(todos));
}, [todos]);
function handleAddInputChange(e) {
setTodo(e.target.value);
}
function handleEditInputChange(e) {
setCurrentTodo({ ...currentTodo, text: e.target.value });
console.log(currentTodo);
}
function handleAddFormSubmit(e) {
e.preventDefault();
if (todo !== "") {
setTodos([
...todos,
{
id: new Date(),
text: todo.trim()
}
]);
}
setTodo("");
}
function handleEditFormSubmit(e) {
e.preventDefault();
handleUpdateTodo(currentTodo.id, currentTodo);
}
function handleDeleteClick(id) {
const removeItem = todos.filter((todo) => {
return todo.id !== id;
});
setTodos(removeItem);
}
function handleUpdateTodo(id, updatedTodo) {
const updatedItem = todos.map((todo) => {
return todo.id === id ? updatedTodo : todo;
});
setIsEditing(false);
setTodos(updatedItem);
}
function handleEditClick(todo) {
setIsEditing(true);
setCurrentTodo({ ...todo });
}
return (
<div className="App">
{isEditing ? (
<EditForm
currentTodo={currentTodo}
setIsEditing={setIsEditing}
onEditInputChange={handleEditInputChange}
onEditFormSubmit={handleEditFormSubmit}
/>
) : (
<AddTodoForm
todo={todo}
onAddInputChange={handleAddInputChange}
onAddFormSubmit={handleAddFormSubmit}
/>
)}
<ul className="todo-list">
{todos.map((todo) => (
<TodoItem
todo={todo}
onEditClick={handleEditClick}
onDeleteClick={handleDeleteClick}
/>
))}
</ul>
</div>
);
}
AddForm.js
file:
// AddForm.js
export default function AddTodoForm({
todo,
onAddFormSubmit,
onAddInputChange,
}) {
return (
<form onSubmit={onAddFormSubmit}>
<h2>Add Todo</h2>
<label htmlFor="todo">Create todo: </label>
<input
name="todo"
type="text"
placeholder="Create new todo"
value={todo}
onChange={onAddInputChange}
/>
</form>
);
}
EditForm.js
file:
// EditForm.js
export default function EditForm({
currentTodo,
setIsEditing,
onEditInputChange,
onEditFormSubmit,
}) {
return (
<form onSubmit={onEditFormSubmit}>
<h2>Edit Todo</h2>
<label htmlFor="updateTodo">Update todo: </label>
<input
name="updateTodo"
type="text"
placeholder="Update todo"
value={currentTodo.text}
onChange={onEditInputChange}
/>
<button type="submit" onClick={onEditFormSubmit}>
Update
</button>
<button onClick={() => setIsEditing(false)}>Cancel</button>
</form>
);
}
Todoitem.js
file:
// TodoItem.js
export default function TodoItem({ todo, onEditClick, onDeleteClick }) {
return (
<li key={todo.id}>
{todo.text}
<button onClick={() => onEditClick(todo)}>Edit</button>
<button onClick={() => onDeleteClick(todo.id)}>Delete</button>
</li>
);
}
This is the fifth post in this series. Hope you enjoyed reading! If you have any suggestions/feedback, please let me know.
Thanks for checking out this series!