0
votes

I'm currently learning React and am following this guy Youtube Videos.

However, they are outdated and it's difficult to get along with the old codes. I already tried some Stackoverflow and Google solutions, but they didn't help.

Right now am I struggling on how I can access my variable todo which I declared inside render() and update it with a my function handleDelete outside of render()?

My goal is to delete the item i clicked on.

I already tried to set my variable inside constructor() but then it wasn't possible to give it the value of this.props.todos.

My code:

import React from 'react';
import ReactDom from 'react-dom';

export default class TodoItem extends React.Component {

  handleDelete(item){
    let updatedTodos = this.props.todos;
    updatedTodos = updatedTodos.filter((val,index) => {
      return item !== val;
    })
    todos = updatedTodos;
  };

  render() {
    //Add all this.props items
    let todos = this.props.todos;
    todos = todos.map((item, index) => {

      return (
      <li>
        <div className="todo-item">
          <span className="item-name">{item}</span>
          <span className="item-remove" onClick={this.handleDelete.bind(this, item)}> x </span>
        </div>
      </li>);
    });


    return (<React.Fragment>{todos}</React.Fragment>)
  };
}

(This code is later exported to index.js where it is transpiled with Babel)

Thanks for your time taken!

Update:

Here is index.js:

import React from 'react';
import ReactDom from 'react-dom';
import TodoItem from './todoItem'; 

class TodoComponent extends React.Component {
    constructor(props) {
      super(props);
      this.state = {
        todos: ["clean up", "walk doggo", "take nap"]
      };
    }
    render() {

      return (<div>

        <h1>The todo list:</h1>
        <ul>
          <TodoItem todos={this.state.todos}/>
        </ul>
      </div>);
    }

  }

  ReactDom.render(<TodoComponent/>, document.querySelector(".todo-wrapper"));
3
You are getting todos as a prop so it should be in a parent component's state. Like todos you need a delete handler from your parent component as a prop. Then you will use this function in your handleDelete function and pass the todos. Can we see your parent component as well?devserkan
your second return statment would never fireShubham Agarwal Bhewanewala
@ShubhamAgarwalBhewanewala, second return belongs to render. First one is for map. So, actually it should work.devserkan
Ah.. what is an item?? an object or string or numberShubham Agarwal Bhewanewala
I suggest that you learn about variable scope in JavaScript. Also learn about state management in ReactJS.Code-Apprentice

3 Answers

3
votes

TodoComponent

import React from 'react';
import ReactDom from 'react-dom';
import TodoItem from './todoItem'; 

class TodoComponent extends React.Component {
    constructor(props) {
      super(props);
      this.state = {
        todos: ["clean up", "walk doggo", "take nap"]
      };
    }
    handleDelete(item){
        let todos = this.state.todos;
        todos= todos.filter((todo) => todo !== item);
        this.setState((prevState) => ({
            todos: todos
        }));
    };
    render() {

      return (<div>

        <h1>The todo list:</h1>
        <ul>
          <TodoItem todos={this.state.todos} handleDelete={this.handleDelete}/>
        </ul>
      </div>);
    }

  }

  ReactDom.render(<TodoComponent/>, document.querySelector(".todo-wrapper"));

Todo Item

import React from 'react';
import ReactDom from 'react-dom';

export default class TodoItem extends React.Component {
    render() {
        return ( 
            this.props.todos.map((item) => {
            return (
                <li>
                    <div className="todo-item">
                    <span className="item-name">{item}</span>
                    <span className="item-remove" onClick={() => this.props.handleDelete(item)}> x </span>
                    </div>
                </li>
            )
        }))
    }
}

Your code is having the problem in TodoItem that you are trying to delete items in TodoItem where you do not have access to state. And moreover If you do some actions in component and you want to get the change reflected the your components must re render. And this is possible when your state is changed. And component related to corresponding changes will re render itself

1
votes

I have not tested it so there might be some typos but you have to do it like this

import React from 'react';
import ReactDom from 'react-dom';

export default class TodoItem extends React.Component {

handleDelete(item){
   this.props.updateTodos(item)
};

render() {
//Add all this.props items
 let todos = this.props.todos;
 todos = todos.map((item, index) => {

  return (
  <li>
    <div className="todo-item">
      <span className="item-name">{item}</span>
      <span className="item-remove" onClick={this.handleDelete.bind(this, item)}> x </span>
    </div>
  </li>);
 });


 return (<React.Fragment>{todos}</React.Fragment>)
};
}



import React from 'react';
import ReactDom from 'react-dom';
import TodoItem from './todoItem'; 

class TodoComponent extends React.Component {
constructor(props) {
  super(props);
  this.state = {
    todos: ["clean up", "walk doggo", "take nap"]
  };
  this.updateTodos =this.updateTodos.bind(this);
}
 updateTodos(item){
   this.setState({todos :this.state.todos.filter((val,index) => {
     return item !== val;
   })})
}
render() {

  return (<div>

    <h1>The todo list:</h1>
    <ul>
      <TodoItem todos={this.state.todos} updateTodos ={this.updateTodos}/>
    </ul>
  </div>);
}

}

 ReactDom.render(<TodoComponent/>, document.querySelector(".todo-wrapper"));
1
votes

I'm suggesting a different approach for you. There are some issues you need to think better. First of all, I suggest keeping your todos as objects and let have them id and text properties.

Second, you have a separate component but you are passing to it whole todos. Instead of that, map your todos and then pass each todo to your component. In this way your management over everything will be easier.

For your situation, you need a delete handler to pass your TodoItem, then by using this handler you will do the action.

Lastly, your TodoItem does not need to be a class component, so I've changed it.

Here is a working example. I've changed your code according to my suggestions.

class Todos extends React.Component {
  state = {
    todos: [
      { id: 1, text: "foo" },
      { id: 2, text: "bar" },
      { id: 3, text: "baz" },
    ]
  }

  deleteTodo = ( todo ) => {
    const newTodos = this.state.todos.filter( el => el.id !== todo.id );
    this.setState({ todos: newTodos });
  }

  render() {
    const { todos } = this.state;
    return (
      <div>
        <ul>
        {
          todos.map( todo => <TodoItem key={todo.id} todo={todo} deleteTodo={this.deleteTodo} /> )
        }
        </ul>
      </div>
    )
  }
}

const TodoItem = props => {
  const handleDelete = () => props.deleteTodo(props.todo);

  return (
    <li onClick={handleDelete}>{props.todo.text} x</li>
  )
};

ReactDOM.render(<Todos />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>

Also, last night I wrote a small example for another question. But it got bigger and bigger and I did not post it. But here is a small, simple Todo example. Of course there must be some improvements but you can examine it as an example.

https://codesandbox.io/s/01v3kmp9vv

Edit

If you don't want to use class-fields and install another Babel plugin, just change the related part with this:

class Todos extends React.Component {
  consturctor( props ) {
      super( props );
      this.state = {
          todos: [
          { id: 1, text: "foo" },
          { id: 2, text: "bar" },
          { id: 3, text: "baz" },
        ],
  }
}
....rest of the code