Building an Awesome Todo List App in React

Building an Awesome Todo List App in React

  • 338

Building an Awesome Todo List App in React.This is the first part of my React tutorials. See the Intro to react.js here. Let's get started with simple example to create todo list

Tools to follow along

  • NodeJS and npm: install from here, if you haven’t
  • Install create-react app with npm install create-react-app --global

Create a new react app

The new react app is created with

create-react-app react-todo

It’ll create a new folder react-todowith some files and folders.

The files and folders in react-todo

package.jsoncontains the list of modules that application uses. Each module performs a function or a set of functions.
node_modulesstores all packages that are listed in package.json.
srccontains all React source code files.
publiccontains static files such as stylesheets and images.

Run this app with npm start

cd react-todonpm start

You’ll get a Welcome page…

This is image title

As the instructions say, modify the App.js to make changes. App.jsis rendered from the index.jsfile. Consider App.jsas the container for all other components.

Experience the ToDo app below. Enter the task in the input box, click on add button to add to the list. To remove from the list, click on the todo to be removed.

Go to the App.jsfile in src . Remove everything from the return except the parent div. It looks like below

import React, { Component } from 'react'
import './App.css'
class App extends Component {
  render() {
    return (
      <div className="App">
      </div>
    )
  }
}
export default App

All components will go inside the div in this return statement.

Creating the User Interface

Todo list is form and a display below as you’ve experienced above.

We create the component TodoListinside TodoList.js file in the src directory.

We import this is the App.js so that we can put this in the div we’re talking about.

This is image title

Also, put the TodoList in the div in return statement.

This is image title

What’s in the TodoList?

import React, { Component } from 'react'
class TodoList extends Component {
  render() {
    return (
      <div className="todoListMain">
        <div className="header">
          <form>
            <input placeholder="Task" />
            <button type="submit"> Add Task </button>
          </form>
        </div>
      </div>
    )
  }
}
export default TodoList

You might recognize some redundant divs, don’t worry we’re filling them in a while.

This component creates the form.

It looks like this…

This is image title

Your output won’t same as mine because of the CSS. I secretly added the CSS to index.cssfile. It’s basic stuff and we’re not discussing about stylesheet after this. If you want your app styled like in the example, grab the index.css from here

If you try to add the todo in this app, it’ll just reload the page. This is because the default behavior of the form is to submit to the same page.

Giving React Todo App a Life

We already have a lifeless todo app, which does nothing other than displaying itself.

Here’s what we’ll be doing:

  • Adding items
  • Displaying items
  • Removing items

Adding Items

The input items are submitted when the form is submitted. To handle this operation, add the onSubmit to form tag in the TodoList.

This is image title

This addItemshould be handled at the App component. It is passed to other components as a prop.

This is image title

This must exist in App to pass. Create a addItemproperty in the App.

We could declare this as an old JavaScript (ES5) like function, but it won’t bind the form with it. We have to bind it manually through the constructor. I’d get rid of this using ES6 like syntax.

We still need the state to hold the array of items. The state makes it easy to render and elements on the page. All components using data will change automatically when the data in state changes.

We also need another state called currentItemto hold the current value in the memory. It is an object and it also has key along with the text. React uses this key internally to only render the components when there are multiple similar components. The to-do list cannot be rendered without a key as there will be all lis.

Add a constructor to the App. Also, add addItemand handleInputto the same.

The addItem manages to add to the list, and handleInput manages the change in the input field.

This is what my App.js looks like…

import React, { Component } from 'react'
import './App.css'
import TodoList from './TodoList'
class App extends Component {
  constructor() {
    super()
    this.state = {
      items: [],
      currentItem: {text:'', key:''},
    }
  }
  handleInput = e => {
    console.log('Hello Input')
  }
  addItem = () => {
    console.log('Hello Add Item')
  }
  render() {
    return (
      <div className="App">
        <TodoList addItem={this.addItem} />
      </div>
    )
  }
}
export default App

To get the input element we must have a way to refer to it. You might be excited to use querySelector, but the React doesn’t like that. While it’s totally valid, the idea of virtual DOM is not to directly interact with the DOM but the components in the DOM.

To refer to the input, we create a refwith inputElement =React.createRef(). Pass this to TodoListjust like the addItem

inputElement = {this.inputElement}

Use it as ref = {this.props.inputElement}in the TodoList.

This is image title

If you try the app at this moment, you can see it logs the message from addItem and then reloads. The reloading is the default behavior of the form submission.

To stop this behavior modify the addItem to the following.

addItem = e => {
    e.preventDefault()
    console.log('Hello World')
  }

The preventDefaultwill prevent reloading from submitting the form.

Here’s all the data we pass to TodoList…

<TodoList
          addItem={this.addItem}
          inputElement={this.inputElement}
          handleInput={this.handleInput}
          currentItem={this.state.currentItem}
        />

addItemto handle click on add.

inputElementto refer to this element.

handleInputto handle data on input field on a change

currentItemto display the value of the state set to currentItem.

Here’s what my TodoList.js looks like…

import React, { Component } from 'react'
class TodoList extends Component {
  componentDidUpdate() {
    this.props.inputElement.current.focus()
  }
  render() {
    return (
      <div className="todoListMain">
        <div className="header">
          <form onSubmit={this.props.addItem}>
            <input
              placeholder="Task"
              ref={this.props.inputElement}
              value={this.props.currentItem.text}
              onChange={this.props.handleInput}
            />
            <button type="submit"> Add Task </button>
          </form>
        </div>
      </div>
    )
  }
}
export default TodoList

We’ll talk about eh ComponentDidUpdatein a while…

formon submit, calls addItem

refrefers to the current element.

valueis stored as text in the currentElementobject.

If you do not have onChangein the component, the field will be read only. We do not want this.

onChangecalls handleInput and that’s next to discuss.

handleInput = e => {
    const itemText = e.target.value
    const currentItem = { text: itemText, key: Date.now() }
    this.setState({
      currentItem,
    })
  }

The handleInput gets the event, it gets the value from the input box and sets the state to and object of currentItem. It has key as current data and text as the input data. The key is Date.now() which is the number of milliseconds from 1970 to now. It can only take a maximum of 1 input per millisecond. That’s enough for our case.

We need this object because we need to store this value to the array itemswhen user submits the form.

addItem = e => {
    e.preventDefault()
    const newItem = this.state.currentItem
    if (newItem.text !== '') {
      console.log(newItem)
      const items = [...this.state.items, newItem]
      this.setState({
        items: items,
        currentItem: { text: '', key: '' },
      })
    }
  }

The addItemprevents default reload. It gets the value in the input box from the state currentItem.

Since we do not want to add empty value to our todo, we check for that. If it’s not empty, items array is destructured and newItemis added.

We have to set this items[] to the state, we call this.setState. It also makes sense to reset the currentItemto clear the input box.

ComponentDidUpdateis one of the lifecycle methods in React. We’ve talked about all of them here. ComponentDidUpdate is called to focus on the input box referred by the inputElementreference. The component is updated on submitting the form. this.props.inputElement.current.focus()sets the focus in the input area so we can continue typing the next item in the todo list.

Displaying the Todos

We have all the todos in the state, all we need is another component that can render these on the screen.

We’ll call this component TodoItemsand pass all items as a prop.

This is image title

Here’s what TodoItemslooks like…

import React, { Component } from 'react'
class TodoItems extends Component {
  createTasks(item) {
    return <li key={item.key}>{item.text}</li>
  }
  render() {
    const todoEntries = this.props.entries
    const listItems = todoEntries.map(this.createTasks)
    return <ul className="theList">{listItems}</ul>
  }
}
export default TodoItems

The function createTasks returns li for each passed item. It uses the key we provided earlier. It will not work with a key at this stage because React must be able to differentiate between the multiple items to re-render the appropriate one.

All these list items are saved to listItemsusing a mapping function. This gets used in the ulin return statement.

Removing the todo

We have added the ToDo, we probably want to remove some.

We already have the displaying todos in the TodoItems.js, we make a small modification. Just add an onClick listener to deleteItem with the key.

createTasks = item => {
    return (
      <li key={item.key} onClick={() => this.props.deleteItem(item.key)}>
        {item.text}
      </li>
    )
  }

This executes deleteItem with the key as a parameter. The prop has to be passed from the App.
<TodoItems entries={this.state.items}deleteItem={this.deleteItem}/>
Create a new property in App.js as deleteItem.

deleteItem = key => {
    const filteredItems = this.state.items.filter(item => {
      return item.key !== key
    })
    this.setState({
      items: filteredItems,
    })
  }

It filters the received key from the itemsstate. Then sets the items to filtered items.