Abaga
Abaga

Reputation: 853

The event listener can't access my handleChange function

App.js (parent component)

import React from "react";
import TodoItem from "./TodoItem";
import todosData from "./todosData";
import "./styles.css";

class App extends React.Component {
  constructor() {
    super();
    this.state = {
      todos: todosData
    };
    this.handleChange = this.handleChange.bind(this);
  }

  handleChange(id) {
    console.log(`${id} Clicked!`);
  }

  render() {
    const todoItems = this.state.todos.map(function (item) {
      return (
        <TodoItem
          key={item.id}
          text={item.text}
          completed={item.completed}
          item={item}
          handleChange={this.handleChange}
        />
      );
    });
    return <div className="todo-list">{todoItems}</div>;
  }
}

export default App;

TodoItem.js (child component)

import React from "react";

function TodoItem(props) {
  return (
    <div className="todo-item">
      <input
        type="checkbox"
        checked={props.completed}
        onChange={() => props.handleChange(props.item.id)}//PROBLEM LINE
      />
      <p>{props.text}</p>
    </div>
  );
}

export default TodoItem;

Returns the following:

CONSOLE: Could not consume error: Error {}

PROBLEMS: props.handleChange is not a function

I used binding and I tested to see that my handleChange() was properly defined. Other props are being read but props.handleChange isn't.

Upvotes: 1

Views: 257

Answers (4)

samo0ha
samo0ha

Reputation: 3798

you need to bind the map function or use the arrow function. because in this case the this in 'this.handleChange' refers to the map's callback function and not to the class that is why it gives you undefined. so you have two solutions for that problem.

soln#1 : arrow function

render() {
        const todoItems = this.state.todos.map( (item) => {
          return (
            <TodoItem
              key={item.id}
              text={item.text}
              completed={item.completed}
              item={item}
              handleChange={this.handleChange}
            />
          );
        });
        return <div className="todo-list">{todoItems}</div>;
     }

soln#2: bind map's callback function

render() {
        const todoItems = this.state.todos.map( function(item) {
          return (
            <TodoItem
              key={item.id}
              text={item.text}
              completed={item.completed}
              item={item}
              handleChange={this.handleChange}
            />
          );
        }.bind(this));
        return <div className="todo-list">{todoItems}</div>;
     }

Upvotes: 0

ANaemi
ANaemi

Reputation: 88

just change your return function to use an arrow function like:

render() {
    const todoItems = this.state.todos.map((item) => {
      return (
        <TodoItem
          key={item.id}
          text={item.text}
          completed={item.completed}
          item={item}
          handleChange={this.handleChange.bind(this, item.id)}
        />
      );
    });
    return <div className="todo-list">{todoItems}</div>;
  }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

and change your TodoItem component to something like this:

import React from "react";

function TodoItem(props) {
  return (
    <div className="todo-item">
      <input
        type="checkbox"
        checked={props.completed}
        onChange={props.handleChange} //PROBLEM LINE
      />
      <p>{props.text}</p>
    </div>
  );
}

export default TodoItem;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

demo here

Upvotes: 0

Talmacel Marian Silviu
Talmacel Marian Silviu

Reputation: 1736

Your problem is here:

const todoItems = this.state.todos.map(function (item) {
  return (
    <TodoItem
      key={item.id}
      text={item.text}
      completed={item.completed}
      item={item}
      handleChange={this.handleChange}
    />
  );
});

When you say this.handleChange in here you are trying to get handleChange from the function which receives 'item' in the map. handleChange is not defined in that function.

So you need to change the function which receives item to an arrow function which take as this the this of the outer function. Like this:

const todoItems = this.state.todos.map(item => 
        <TodoItem
          key={item.id}
          text={item.text}
          completed={item.completed}
          item={item}
          handleChange={this.handleChange}
        />
      );

Upvotes: 2

Moukim hfaiedh
Moukim hfaiedh

Reputation: 409

class TestTodo extends React.Component {

    state = {
      todos: todosData
    };
  

  handleChange = (id) => {
    console.log(`${id} Clicked!`);
  }

  render() {

    const todoItems = this.state.todos.map( item=> {
      return (
        <TodoItem
          key={item.id}
          text={item.text}
          completed={item.completed}
          item={item}
          handleChange={this.handleChange}
        />
      );
    });
    return <div className="todo-list">{todoItems}</div>;
  }
}

export default TestTodo;


import React from "react";

const  TodoItem = props => {
    const handleChange = () =>{
        props.handleChange(props.item.id)
    }

  return (
    <div className="todo-item">
      <input
        type="checkbox"
        checked={props.completed}
        onChange={handleChange}
      />
      <p>{props.text}</p>
    </div>
  );
}

export default TodoItem;

Upvotes: 2

Related Questions