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.

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.

Next part: Persisting React State in Local Storage

continue