Kit
Kit

Reputation: 359

TypeError: Cannot read property 'handleClick' of undefined

I'm learning React and I'm trying to do a little project. This project is showing a list of users of a jsonPlaceholder and wanted to make it to edit the fields.

I have a div that loads the list, another hidden div with editable fields and a button that when clicked the list div is hidden and the other one appears.

When I click the button it gives the following error:

"TypeError: Can not read property 'handleClick' of undefined"

Can anyone help?

import React from 'react';
import './App.css';

class User extends React.Component {
  constructor(){
    super();
    this.state = {
      users: [],
      displayList: 'block',
      displayForm: 'none'
    }
    this.handleClick = this.handleClick.bind(this);
  }

  componentDidMount(){
    this.getUsers();
  }

  getUsers(){
    fetch('https://jsonplaceholder.typicode.com/users')
      .then(response  => response.json())
      .then(json => this.setState({users: json}));
  }

  handleClick(){
    this.setState({displayList: 'none', displayForm: 'block'})
  }

  render(){
    const list = this.state.displayList;
    const form = this.state.displayForm;

    return (
      <ul>
        {this.state.users.map(function(item){
          return (
            <div key={item.id}>
              <br></br>

              <div style={{display: list}}>
                <h2>{item.name} </h2>
                <p>Email: {item.email} </p>
                <p>Address: </p>
                <ul>
                  <li>Street: {item.address.street} </li>
                  <li>Suite: {item.address.suite} </li>
                  <li>City: {item.address.city} </li>
                  <li>ZipCode: {item.address.zipcode} </li>
                </ul>
                <p>Phone: {item.phone} </ p>
              </div>

              <div style={{display: form}} >
                <div>
                  <h2>{item.name} </h2>
                  <p>Email: <input value = {item.email} className="form-control" onChange=""/> </p>
                  <p>Address: </p>
                  <ul>
                    <li>Street: <input value = {item.address.street} className="form-control" /> </li>
                    <li>Suite: <input value = {item.address.suite} className="form-control" /> </li>
                    <li>City: <input value = {item.address.city} className="form-control" /> </li>
                    <li>ZipCode: <input value = {item.address.zipcode} className="form-control" /> </li>
                  </ul>
                  <p>Phone: <input value = {item.phone} className="form-control" /> </p>
                </div>
              </div>

              <button onClick={() => this.handleClick()}> Edit </button>
              <hr></hr>
            </div>
          )
        })}
      </ul>
    );
  }
}

export default User;

Upvotes: 0

Views: 1332

Answers (3)

antonku
antonku

Reputation: 7665

The issue is that this.handleClick is referenced inside a callback defined as a regular function that is passed to a map. Regular function have their own values of this which is not the component.

In order to retain the value of this you call pass the callback to the map as an arrow function:

render () {
    /* ... */
    return (
      <ul>
        {this.state.users.map((item) => { /* ... */ })}
      </ul>
    )
}

Alternatively you can explicitly pass this value as a second argument to the map

render () {
    /* ... */
    return (
      <ul>
        {this.state.users.map(function(item) { /* ... */ }, this)}
      </ul>
    )
}

Upvotes: 0

The Witness
The Witness

Reputation: 920

You have to bind the method to the object.

Either:

handleClick = () => {
    this.setState({displayList: 'none', displayForm: 'block'})
}

or:

<button onClick={this.handleClick.bind(this)}> Edit </button>

Otherwise it loses the context.

Upvotes: 0

Joey Gough
Joey Gough

Reputation: 3103

It is because the callback in the map method this.state.users.map is not bound. You need to turn that into an arrow function. like this:

import React from 'react';
import './App.css';

class User extends React.Component {
  constructor(){
    super();
    this.state = {
      users: [],
      displayList: 'block',
      displayForm: 'none'
    }
    this.handleClick = this.handleClick.bind(this);
  }

  componentDidMount(){
    this.getUsers();
  }

  getUsers(id){
    fetch('https://jsonplaceholder.typicode.com/users')
      .then(response  => response.json())
      .then(json => this.setState({users: json}));
  }

  handleClick(){
    this.setState({displayList: 'none', displayForm: 'block'})
  }

  render(){
    const list = this.state.displayList;
    const form = this.state.displayForm;

    return (
      <ul>
        {this.state.users.map((item => {
          return (
            <div key={item.id}>
              <br></br>

              <div style={{display: list}}>
                <h2>{item.name} </h2>
                <p>Email: {item.email} </p>
                <p>Address: </p>
                <ul>
                  <li>Street: {item.address.street} </li>
                  <li>Suite: {item.address.suite} </li>
                  <li>City: {item.address.city} </li>
                  <li>ZipCode: {item.address.zipcode} </li>
                </ul>
                <p>Phone: {item.phone} </ p>
              </div>

              <div style={{display: form}} >
                <div>
                  <h2>{item.name} </h2>
                  <p>Email: <input value = {item.email} className="form-control" onChange=""/> </p>
                  <p>Address: </p>
                  <ul>
                    <li>Street: <input value = {item.address.street} className="form-control" /> </li>
                    <li>Suite: <input value = {item.address.suite} className="form-control" /> </li>
                    <li>City: <input value = {item.address.city} className="form-control" /> </li>
                    <li>ZipCode: <input value = {item.address.zipcode} className="form-control" /> </li>
                  </ul>
                  <p>Phone: <input value = {item.phone} className="form-control" /> </p>
                </div>
              </div>

              <button onClick={this.handleClick}> Edit </button>
              <hr></hr>
            </div>
          )
        }))}
      </ul>
    );
  }
}

export default User;

Upvotes: 2

Related Questions