cjl85
cjl85

Reputation: 165

React - Passing Props

Currently, it's giving me an error saying that isCompleted is undefined.

todo.isCompleted = todo.isCompleted ? false : true;

The above code is what's throwing the error.

The advice Im getting is to pass a todoIndex prop in App.js when you're rendering the Todo component.

Not sure how to go about doing that. Any pointers?

import React, { Component } from 'react';
import './App.css';
import ToDo from './components/ToDo.js';

class App extends Component {
 constructor(props) {
   super(props);
    this.state = {
     todos: [
       { description: 'Walk the cat', isCompleted: true },
       { description: 'Throw the dishes away', isCompleted: false },
       { description: 'Buy new dishes', isCompleted: false }],
     newTodoDescription: ''
  };
}

deleteToDo(deleteToDo) {
  console.log(this);
  let newToDos = this.state.todos.filter((todo) => {
    return todo !== deleteToDo
  } )
  this.setState({ todos: newToDos });
}

handleChange(e) {
 this.setState({ newTodoDescription: e.target.value })
}

handleSubmit(e) {
  e.preventDefault();
  if (!this.state.newTodoDescription) { return }
  const newTodo = { description: this.state.newTodoDescription, isCompleted: false };
  this.setState({ todos: [...this.state.todos, newTodo], newTodoDescription: '' });
}

toggleComplete(index) {
  const todos = this.state.todos.slice();
  const todo = todos[index];
  todo.isCompleted = todo.isCompleted ? false : true;
  this.setState({ todos: todos });
}


render() {
 return (
  <div className="App">
    <ul>
    { this.state.todos.map( (todo, index) =>
      <ToDo key={ index } description={ todo.description } isCompleted={ todo.isCompleted } toggleComplete={ this.toggleComplete.bind(this) } deleteToDo={() => this.deleteToDo(todo)} />

    )}
    </ul>
    <form onSubmit={ (e) => this.handleSubmit(e) }>
     <input type="text" value={ this.state.newTodoDescription } onChange={ (e) => this.handleChange(e) } />
     <input type="submit" />
   </form>
  </div>
);
}
}

export default App;

ToDo.js

import React, { Component } from 'react';

class ToDo extends Component {

  toggleComplete = () => {
    this.props.toggleComplete(this.props.todoIndex)
  }

  render() {

    return (

    <ul>
     <input type= "checkbox" checked= { this.props.isCompleted } 
    onChange= { this.handleToggleClick.bind(this)} />
     <span>{ this.props.description }</span>
     <button onClick={ this.props.deleteToDo }> X </button>
  </ul>

  );
 }
}
export default ToDo;

Upvotes: 0

Views: 214

Answers (2)

Hamza Essolami
Hamza Essolami

Reputation: 31

You're not passing the index to ToDo, so toggleComplete doesn't know which todo to update.

Fix in App.js

Pass index as a prop:

<ToDo 
  key={index} 
  description={todo.description} 
  isCompleted={todo.isCompleted} 
  toggleComplete={() => this.toggleComplete(index)} // Pass index
  deleteToDo={() => this.deleteToDo(todo)} 
/>

Fix in ToDo.js

Update toggleComplete to call the prop directly:

<input 
  type="checkbox" 
  checked={this.props.isCompleted} 
  onChange={this.props.toggleComplete} // No need for another function
/>

Upvotes: 0

Stefan
Stefan

Reputation: 675

To pass the index to the ToDo component add another prop:

<ToDo key={index} todoIndex={index} ... />

and make sure that the component calls toggleComplete with that index prop, i.e.

class ToDo extends React.Component {
  toggleComplete() {
    this.props.toggleComplete(this.props.todoIndex)
  }
}

Also, you're mutating the todo object in your toggleComplete function, instead of:

todo.isCompleted = todo.isCompleted ? false : true;

Better do this:

const todos[index] = {...todo, isCompleted: !todo.isCompleted }

Or with Object.assign:

const todos[index] = Object.assign({}, todo, {isCompleted: !isCompleted})

I think Max Kurtz answer is also correct, the this binding seems to be problematic. Bind the toggleComplete function in the constructor or use arrow functions to make sure this doesn't bite you.

Upvotes: 2

Related Questions