How to Edit Todos Items in a React Application
With the current status of our app, we can add todos items, unmark/mark completed and as well as delete items. That’s is fine for a simple todos application.
In this part of the series, you will learn how to add the edit feature to your todos app. We will take a look at this by following React best practices.
This React tutorial is part 4 of 11 in the React for beginners series.
- Part 1 – React Tutorial: The Beginner's Guide to Learning React in 2020
- Part 2 – Working with React Form and Handling Event
- Part 3 – How to implement CSS in Reactjs App
- Part 5 – Persisting React State in Local Storage
- Part 6 – Getting Started With React Lifecycle Methods
- Part 7 – Getting Started With React Hooks
- Part 8 – How to use SVG Icons in React
- Part 9 – Routing With React Router
- Part 10 – How to add Hamburger Menu in React
- Part 11 – Deploying React App to GitHub Pages
The logic here is simple. Once we double click the item to edit, we will activate the edit mode and display a text input field containing the item to edit so that users can modify.
Let’s get started by opening the TodoItem.js
file. This file handles each of the todos items. Go ahead and wrap all the <li>
children with a <div>
container and add onDoubleClick
event to it.
return (
<li className={styles.item}>
<div onDoubleClick={this.handleEditing}>...</div> </li>
)
Next, add the reference handler above the render()
method.
handleEditing = () => {
console.log("edit mode activated")
}
If you save the file and double click any of the todos items, you’ll see the log message in the console of your DevTools.
Good, we are heading somewhere.
We want to be able to hide the item and display a text field to accept the update.
First, let’s add the text field right before the closing </li>
tag.
return (
<li className={styles.item}>
<div onDoubleClick={this.handleEditing}>...</div>
<input type="text" className={styles.textInput} /> </li>
)
Notice that we included a className
to the text input.
Next, add a state
object in the class component and set the edit mode to false
by default.
state = {
editing: false,
}
We will change this to true
using the setState()
method once we double click any of the items. We will also use the state value to dynamically hide/display the text field.
Now, update the state in the handleEditing
so you have:
handleEditing = () => {
this.setState({
editing: true,
})
}
Remember this function is called once you double click an item.
Still in the file, let’s add the logic that dynamically hides/display the todos/ text field.
Add this in the render()
method but above the return
statement:
let viewMode = {}
let editMode = {}
if (this.state.editing) {
viewMode.display = "none"
} else {
editMode.display = "none"
}
Finally, add these variables (viewMode
and editMode
) as values of the style
attribute in the div
and the input
respectively:
<li className={styles.item}>
<div onDoubleClick={this.handleEditing} style={viewMode}>
...
</div>
<input type="text" style={editMode} className={styles.textInput} />
</li>
Save your file.
Before you check the frontend, let’s style the text field by adding the following styles to the TodoItem.module.css
file:
.textInput {
width: 100%;
padding: 10px;
border: 1px solid #dfdfdf;
}
Save your file and test your work.
Good. Now we can see the edit field on double click.
At the moment, the input field has the default HTML behaviour because it is being handled by the DOM. If you have been following the series from the beginning, you should know that React doesn’t work that way.
The field needs to be a controlled field.
To do this, we need to make sure that the value
prop of the text input is not null or undefined.
So let’s simply update the input
so you have:
<input
type="text"
style={editMode}
className={styles.textInput}
value={title}
/>
In the code, we assigned a default text (i.e the todos title
) to the value
prop. This makes sense because that is what we want to modify.
And remember, we have access to the title
in this component.
Now, you cannot change the edit field text unless you control it. If you open the console, React is already yelling at us to add an onChange
event handler to control it. If you don’t know why this is happening, please revisit the earlier part of the series.
Let’s update the input text to include the onChange
event:
<input
type="text"
style={editMode}
className={styles.textInput}
value={title}
onChange={e => {
console.log(e.target.value, id)
}}
/>
For the meantime, we are logging the text and the input id on every keystroke in the console. But you notice that the warning is gone.
Still, we were not able to edit the todos items.
Remember how we’ve been raising and handling the event in this series? We will do the same thing here!
The todos items that we want to update live in the TodoContainer
component. So we need to raise an event from the TodoItem
component and handle it in the TodoContainer
.
As expected, we need to ensure there is communication between these components.
Starting from the parent, TodoContainer
, add this method above the render()
:
setUpdate = (updatedTitle, id) => {
console.log(updatedTitle, id)
}
The method expects the updated title and the id of the text input performing the action.
Now, let’s pass the method to the TodosList
component through the props.
<TodosList
todos={this.state.todos}
handleChangeProps={this.handleChange}
deleteTodoProps={this.delTodo}
setUpdate={this.setUpdate}
/>
From there, we can pass it to the TodoItem
component. Save the file and head over to the TodosList
component. Update the <TodoItem />
so you have:
<TodoItem
key={todo.id}
todo={todo}
handleChangeProps={this.props.handleChangeProps}
deleteTodoProps={this.props.deleteTodoProps}
setUpdate={this.props.setUpdate}
/>
Finally, in the TodoItem
component, update the onChange
to point to the setUpdate()
method:
<input
type="text"
style={editMode}
className={styles.textInput}
value={title}
onChange={e => {
this.props.setUpdate(e.target.value, id)
}}
/>
Save all files. You should still be able to see the updated text and the input id’s in the console.
What next?
We need to update the items in the TodoContainer
state object.
As expected, we will make use of the setState()
method to do that. So update the setUpdate()
in the TodoContainer.js
file so you have:
setUpdate = (updatedTitle, id) => {
this.setState({
todos: this.state.todos.map(todo => {
if (todo.id === id) {
todo.title = updatedTitle
}
return todo
}),
})
}
The code is straightforward. All we are doing is updating the state todos title whose id
matches that of the edit text input. This title
gets the updated value of the text field.
Save your file and test your work.
Now, we can edit and update the todos title
. But hey, there is one more thing. We need a way to exit the edit mode once the Enter key is pressed.
Detecting when the Users press the Enter key to submit edited Items
As soon as we submit the edited value, we need to trigger a method that reset the edit mode to false
thereby hiding the edit field.
To do this, we will listen for the keydown
event that fires when any key is pressed. Then, we check for the Enter key using the event.key
.
Let’s do it.
In the TodoItem.js
file, add onKeyDown
event to the text input so you have:
<input
type="text"
style={editMode}
className={styles.textInput}
value={title}
onChange={e => {
this.props.setUpdate(e.target.value, id)
}}
onKeyDown={this.handleUpdatedDone}
/>
Then add its handler above the render()
method:
handleUpdatedDone = event => {
console.log(event.key)
}
Save the file.
Open the console and double click any of the todos items to enter the edit mode. Then press the keyboard keys including the Enter key. You’ll see them all displayed as you press.
Good. But instead of that, let’s update the function to this:
handleUpdatedDone = event => {
if (event.key === "Enter") {
this.setState({ editing: false })
}
}
Save the file and test your work. Now the todos app works as intended.
But not until you reload the page.
The UI goes back to default. So what next? We need to persist the data so that we can have access to it on page reload or even on the subsequent visit.
Discussion