Building an Awesome Todo List App

Hello friends,

I would really appreciate it if someone could help me find the bug here.

I’ve followed up to the end of the “adding items” section. Somehow the items array is behind by 1 item according to what is being printed.

For example if I entered “abc”, it prints an empty array. If I then enter “def”, it prints an array with just “abc”. Why is this?

Here’s my TodoList.js:

import React, { Component } from "react";
class TodoList extends Component {
  constructor(props) {
    super(props);

    this.state = {
      items: []
    };

    this.addItem = this.addItem.bind(this);
  }

  addItem(e) {
    if (this._inputElement.value !== "") {
      var newItem = {
        text: this._inputElement.value,
        key: Date.now()
      };
   
      this.setState((prevState) => {
        return { 
          items: prevState.items.concat(newItem) 
        };
      });
     
      this._inputElement.value = "";
    }
     
    console.log(this.state.items);
       
    e.preventDefault();
  }
  
  render() {
    return (
      <div className="todoListMain">
        <div className="header">
          <form onSubmit={this.addItem}>
            {/* this thing here with the ref
            is so that we can access the input element via the
            reference _inputElement
             */}
            <input ref={(a) => this._inputElement = a}
                  placeholder="enter task">
            </input>
            <button type="submit">add</button>
          </form>
        </div>
      </div>
    );
  }
}
 
export default TodoList;

Thanks in advance.

Amazing tutorials and website by the way! Everyone is so nice on here. Wish I found this sooner :heart_eyes:

When you use setState with a function, it doesn’t immediately set the state. It gets deferred for just a little bit meaning your log() call will happen before the state changes. You can read more about this here (the preview just says reactjs.org but it goes to the correct part in the documentation that talks about this):

2 Likes

Interesting - thank you!

Hello! What if we get input value throughout:

addItem(e) {
e.target.elements[0].value?
…

Then you won’t need to use refs

Hi there Kirupa,

In the tutorial, you wrote “we are going to tie together a lot of the concepts and techniques you’ve learned”. Can you specify which tutorials you strongly recommend a newbie to React go through before attempting the simple to-do list app?

Thank you kindly,

Avi

If this is your first jump into React, I would encourage starting at the top itself and going down: https://www.kirupa.com/react/index.htm The concepts build on top of each other.

If you are already familiar with React, it would be a fairly quick review. You can go through the TodoList tutorial and see what you need more details on and focus on those.

Cheers,
Kirupa :smile:

1 Like

Hi Kirupa,
Just wanted to check with you if we could use the event deligation here. We have added the event listener to the ‘li’. Could you please let me know how we can do that by adding the event listener to the ‘ul’ and then do the event delegation. Is it even the right way to do it?
Thanks,
Chandra

With React, the event delegation is done automatically for you. Even if you create event handlers on each element, the listening happens at the document level. That is why you don’t have to make this extra step in React. For regular JS, it is usually a good idea to delegate event handling to a common parent :slight_smile:

Thanks for the tutorial. Instead of

this.functionName = this.functionName.bind(this);
 
functionName() {
//...
}

it looks easier to me to just use an arrow function. So:

functionName = () => {
//...
}

Is there a reason to use traditional functions and the bind method instead? Both seem to work.

You are right in that an arrow function would be more concise. There isn’t a right or wrong way, but I think I will revise this content in the future to just use arrow functions.

I wrote a bit about this here: React, Class Syntax, and Autobinding Shenanigans!

@jersoe You might also be interested in reading Arrow Functions in Class Properties Might Not Be As Great As We Think which talks about some of the differences and why using arrow functions isn’t always a good thing.

I tried to redo the Todo List App with functional components today. The one problem that I have is that the component is not refreshed after the submit method (addItem) is called. So the items do not get shown from the TodoItems component. I am not sure what I did wrong or missed.

Hi @ksanders - can you share your code? It’s hard to know what may be going on there :slight_smile:

Yes. This is my version of the TodoList.js using functional components. I have also included the TodoItems.js as reference. It is using a class component as you did in the tutorial. I have not converted it yet.

import React, { useState, useRef } from 'react'
import TodoItems from './TodoItems'

const TodoList = () => {
  const [items, setItems] = useState([])

  const inputElement = useRef(null)

  const handleSubmit = (e) => {
    e.preventDefault()
    const itemArray = items
    if (inputElement.current.value !== '') {
      itemArray.unshift({
        text: inputElement.current.value,
        key: Date.now()
      })

      setItems(itemArray)
      inputElement.current.value = ''
    }

    console.log('handleSubmit', itemArray)
    console.log('items', items)
  }

  return (
    <div className='todoListMain'>
      <div className='header'>
        <form onSubmit={handleSubmit}>
          <input ref={inputElement} placeholder='enter task'></input>
          <input type='submit' value='add'/>
        </form>
      </div>
      <TodoItems entries={items} />
    </div>
  )
}

export default TodoList


import React, { Component } from 'react'
import FlipMove from 'react-flip-move';

class TodoItems extends Component {
  constructor(props) {
    super(props)

    this.createTasks = this.createTasks.bind(this)
  }

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

  delete(key) {
    this.props.delete(key);
  }

  render() {
    const { entries } = this.props
    console.log('Inside TodoItems', entries)
    var listItems = entries.map(this.createTasks)

      return (
        <ul className='theList'>
          <FlipMove duration={250} easing='ease-out'>
            {listItems}
          </FlipMove>
        </ul>
      )
  }
}

export default TodoItems

Thanks for the code! Does your items array contain an accurate list of the tasks added? If you add a console.log call to createTasks in TodoItems, does it get called for each task added as well?

Yes sir. I just verified that that the items array in the state does have the accurate list each time I add a new task. I added a console.log() in the createTasks call in TodoItems, but it never gets called when I add a task. It is as if the TodoList component is not refreshing on submit.

This is a bit puzzling. Can you replace <TodoItems entries={items} /> with <TodoItems entries={setItems(items)} />

Does that make the example work?

Hello kirupa, why do we need to bind createTasks function, as this function doesnt use any React state?
class TodoItems extends Component {

constructor(props) {

super (props);

this .createTasks = this .createTasks.bind( this );

}
createTasks(item) {

return <li onClick={() => this . delete (item.key)}

key={item.key}>{item.text}</li>

}

Hi @nishaanth_vikram - you are right. If it works without the binding, then it is just an oversight on my part. Something for me to revise in a subsequent edition :slight_smile:

Hi Kirupa! Thank you for the awesome tutorial :slight_smile: I am however having trouble getting the ul list to show. When I type a task and press “add” the entire page goes blank.

" TypeError: todoEntries.map is not a function. (In ‘todoEntries.map(this.createTasks)’, ‘todoEntries.map’ is undefined) " This is the error code I get.