1

I am making a To-Do Application in React. I got stuck at some point.
I'm mapping through items in an array, and displaying it in an unordered list. I'm trying to use the filter function, to remove the deleted items from the array.

I assume that the problem in my code can be somewhere there, that I am passing the event object but pointing to the button, instead of the list item.

How can I do it in React? You can find my code attached below.
Also it would be great to clear the input field after submitting an item.

import React, { Component } from 'react';

class ToDoList extends Component {
  constructor(props) {
    super(props);
    this.state = {list: [], items: ''};
    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
    this.handleRemove = this.handleRemove.bind(this);
  }

  handleChange(event) {
    this.setState({items: event.target.value})
    console.log(event.target.value);
  }

  handleSubmit(event) {
    this.setState({ list: [...this.state.list, this.state.items]})
    event.preventDefault();
  }

  handleRemove(event) {
    const filteredArray = this.state.list.filter(item => item !== event.target.value)
    this.setState({list: filteredArray});
    console.log(event.target);
  }

  render() {
    return (
    <div>
      <form onSubmit={this.handleSubmit}>
        <label>
          <input
            type="text" 
            value={this.state.value}
            onChange={this.handleChange} />
        </label>
        <input
          onClick={this.handleSubmit}
          type="submit"
          value="Submit" />
      </form>
      <ul>
        {this.state.list.map((i, index) => (
            <li key={index+1}>
              {i}
              <button 
                onClick={this.handleRemove}>
                  X
              </button>
            </li>
        ))}
      </ul>
      <p>Remaining: {this.state.list.length}</p>
      </div>
    );
  }
}

export default ToDoList;

2 Answers 2

1

I would recommend using the optional additional arguments to .bind in order to change what's passed to your handler.

Call .bind(this. index) within your map method to pass the index of the element to be removed to the handler:

      <ul>
        {this.state.list.map((i, index) => (
            <li key={index+1}>{i}<button onClick={this.handleRemove.bind(this, index)}>X</button></li>
        ))}
      </ul>

And then update the handler to just remove the specified element:

  handleRemove(index) {
    const filteredArray = this.state.list.filter((_, i) => i !== index);
    this.setState({
      list: filteredArray
    });
  }

As to your second question, you should first fix up your input so its value is actually controlled by the state by changing value={this.state.value} to value={this.state.items}:

<input type="text" value={this.state.items} onChange={this.handleChange} />

And then simply clear this.state.items upon submission:

  handleSubmit(event) {
    this.setState({
      list: [...this.state.list, this.state.items],
      items: ''
    })
    event.preventDefault();
  }
Sign up to request clarification or add additional context in comments.

5 Comments

Yeah, but the cons of this approach is that decrease performance. Using bind inside a prop means that React creates a new brand function on every single render.
Although that's certainly true, I'd consider that an extremely niche concern and not worth worrying about for this specific instance. But if one were to want to correct that, I'm personally a fan of the approach recommended by the eslint react plugin: github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/…
it seems working, thanks! I only have one questions regarding the filtering, what is the first parameter in the arrow function? I mean _, I've tried null but that caused an error.
The first parameter is simply the first argument to the .filter function, which represents the element in the array. Since we care about the second argument (the index of the element) and not so much the first, we still need to include the parameter in the arrow function definition. But since we don't care about it, convention says that we name the parameter _ (or something prefixed with an underscore) to indicate that it's unused
The reason why naming the parameter null causes an error is that null is a keyword
0

Check at this line:

<li key={index+1}>{i}<button onClick={this.handleRemove}>X</button></li>

Inside handleRemove, the instruction event.target point to the button. A button doesn't have a value attribute. So, you need to go up to parent and get it's content:

// childNodes[0] is a TextNode, so, to get it's content use data pop
const toRemove = event.target.parentNode.childNodes[0].data.trim();
const filteredArray = this.state.list.filter(item => item !== toRemove);

Another option is wrap the item in a element, like span:

<li key={index+1}><span>{i}</span><button onClick={this.handleRemove}>X</button></li>

And on remove get it to get the item value:

const toRemove = event.target.parentNode.children[0].textContent.trim();
const filteredArray = this.state.list.filter(item => item !== 
event.target.value)

2 Comments

it seems not working, but thanks, I try experimenting further and investigate the problem.
@CsonkaTamás I updated my answer. The second approach should do it. But I do not sure why the first code isn't working. ev.target is button, .parentNode select it's parent (li) and childNodes on li get back al child nodes. At this point, you should get back two nodes: the text ({i}) and the button. So, you can do a simple console.log of ev.target.parentNode.childNodes to see which nodes you are getting back.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.