React - TypeError: Cannot read property 'props' of undefined

142,792

Solution 1

You rewrite the context of the class method when you pass it to props like this because of JS OOP system. So to make it work there are several approaches:

1) This is not so good because bind always returns new function and your component will re-render even if there are no updates in props

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

class App extends Component {
    render() {
        return (
            <div className="App">
                <StreetFighter />
            </div>
        );
    }
}

class StreetFighter extends Component {
    constructor(props) {
        super(props);
        this.state = {
            characters: [
                'Chun-Li',
                'Guile',
                'Ryu',
                'Ken',
                'E.Honda',
                'Dhalsim',
            ],
        };
    }
    render() {
        let characters = this.state.characters;
        characters = characters.map((char, index) => {
            return (
                <Character char={char} key={index} onDelete={this.onDelete.bind(this)} />
            );
        });
        return (
            <div>
                <p>Street Fighter Characters</p>
                <ul>{characters}</ul>
            </div>
        );
    }
    onDelete(chosenCharacter) {
        let updatedCharactersList = this.state.characters.filter(
            (char, index) => {
                return chosenCharacter !== char;
            }
        );

        this.setState({
            characters: updatedCharactersList,
        });
    }
}

class Character extends Component {
    render() {
        return (
            <li>
                <div className="character">
                    <span className="character-name">{this.props.char}</span>
                    <span
                        className="character-delete"
                        onClick={this.handleDelete.bind(this)}
                        > x </span>
                </div>
            </li>
        )
    };

    handleDelete() {
        this.props.onDelete(this.props.char);
    }
}


export default App;

2) In my code I use arrow functions as class properties for such cases (it's one of the most common solutions, I think)

class Character extends Component {
    render() {
        return (
            <li>
                <div className="character">
                    <span className="character-name">{this.props.char}</span>
                    <span
                        className="character-delete"
                        onClick={this.handleDelete}
                        > x </span>
                </div>
            </li>
        )
    };

    handleDelete = () => {
        this.props.onDelete(this.props.char);
    }
}

Solution 2

TLDR: The specific problem in your code is stated in the paragraph near the end of this answer.

This is a classical problem with JavaScript's this, I suggest you read a little bit into it if you haven't already.

To put it in short and simpler terms (not just for you, but if someone else is reading this), a JavaScript function definition (if not written as an arrow function) redefines what this is, i.e. what it is pointing to. So when you define it:

handleDelete() {
     this.props.onDelete(this.props.char);
}

That function's this is not pointing to the object instance of the class it is defined in. This is a bit counter-intuitive if you're coming from a C++/C#/Java background. The thing is that this exited way before classes came into JavaScript and classes are noting more than a syntax sugar for a function with a bunch of defined prototypes (see here), or in other words it does not bind this to its functions by default.

 

There are a couple typical ways around this:

 

Bind this to all functions (in the constructor)

class Character extends Component {
    constructor(props) {
        super(props)
        this.handleDelete = this.handleDelete.bind(this)
    }
    render() {
        // ...
    };

    handleDelete() {
        this.props.onDelete(this.props.char);
    }
}

NOTE: Instead of this you can bind this on every use of the function (i.e. onClick={this.handleDelete.bind(this)}, but it's not advisable because it will make you're code prone to errors if you ever forget to bind this. Also if you're chaining functions, you might point to the wrong thing somewhere. Not to mention that bind is a function, and in React you will be making a function call on every render. However, it is a good thing to keep in mind if you ever have a situation in which you have to to change this.

 

Use arrow functions

class Character extends Component {
    render() {
        // ...
    };

    handleDelete = () => {
        this.props.onDelete(this.props.char);
    }
}

As stated above, and in the other answers, arrow functions do not redefine the this pointer. What you're effectively doing here is assigning the arrow function to an attribute of the object instance of this class. In other words the function (being an arrow function that does not redefine this) takes the this from the outer scope (the scope of the class), however, because arrow functions are anonymous functions, you name it by assigning it to a name property.

 

All other solutions are some variations of the two above

 


Concerning your solution

Both onDelete and handleDelete suffer from the this issue.

Also, as @Alyson Maia has stated above, your Character component can be written as a functional component:

const Character = (props) => {
    render() {
        return (
           <li>
                <div className="character">
                    <span className="character-name">{this.props.char}</span>
                    <span
                        className="character-delete"
                        onClick={props.onDelete(props.char)}
                        > x </span>
                </div>
           </li>
        )
    };
}

Solution 3

When you create a function to handle an event, don't forget to add it to your props through the constructor as following:

constructor (props) {
  super(props)
  this.yourFunction = this.yourFunction.bind(this)
}

Solution 4

Whats happening is when you use this inside handleDelete you are not referencing the class. You can work around this problem with the followwing approches

Using stateless components (best approche in your case)

Components that dont change the state, dont need to be Class, you can define them as functions or constants

Class Parent extends React.Component {
  state = { ... }

  onDelete = () => { ... }

  render() {
    return (
      <Child onDelete={this.onDelete} />
    )
  }
}

function Child(props) {
  return (
    <button onClick={props.onDelete}>Delete</button>
  )
}

Using arrow functions

Arrow functions dont define a scope, inside an arrow function you are in the class scope.

Class Parent extends React.Component {
  state = { foo: 'bar' }

  wrongMethod() {
    console.log(this.state) // undefined
  }

  rightMethod = () => {
    console.log(this.state) // { foo: 'bar' }
  }

  render() {
    this.wrongMethod()
    this.rightMethod()
    return (
      <h1>Hello World!</h1>
    )
  }
}

Using bind

If you have a method that uses this you have to bind the method scope to the class scope, this can be made like folowing. The bindOnRender have performance issues due to be called on every render and creating a new function on each call.

Class Parent extends React.Component {
  constructor() {
    this.state = { foo: 'bar' }

    this.bindOnConstructor.bind(this)
  }

  bindOnConstructor() {
    console.log(this.state) // { foo: 'bar' }
  }

  bindOnRender = () => {
    console.log(this.state) // { foo: 'bar' }
  }

  render() {
    return (
      <button onClick={this.bindOnConstructor}>Foo</button>
      <button onClick={this.bindOnRender.bind(this)}>Bar</button>
    )
  }
}

Solution 5

By using arrow function, you can solve the this context. Try this:

Your onClick event onClick={this.handleDelete}

and your function definition:

handleDelete = () => {
    //here you can access the this.props
}
Share:
142,792
pyan
Author by

pyan

Updated on January 27, 2022

Comments

  • pyan
    pyan over 2 years

    I'm trying to create a click event be able to delete an item on my list, but when I click it I get "TypeError: Cannot read property 'props' of undefined".

    I'm trying to stick to ES6 as much as possible, and I'm pretty sure its something to do binding 'this' somewhere, but I've tried many places and been unsuccessful.

    import React, { Component } from 'react';
    import './App.css';
    
    class App extends Component {
        render() {
            return (
                <div className="App">
                    <StreetFighter />
                </div>
            );
        }
    }
    
    class StreetFighter extends Component {
        constructor(props) {
            super(props);
            this.state = {
                characters: [
                    'Chun-Li',
                    'Guile',
                    'Ryu',
                    'Ken',
                    'E.Honda',
                    'Dhalsim',
                ],
            };
        }
        render() {
            let characters = this.state.characters;
            characters = characters.map((char, index) => {
                return (
                    <Character char={char} key={index} onDelete={this.onDelete} />
                );
            });
            return (
                <div>
                    <p>Street Fighter Characters</p>
                    <ul>{characters}</ul>
                </div>
            );
        }
        onDelete(chosenCharacter) {
            let updatedCharactersList = this.state.characters.filter(
                (char, index) => {
                    return chosenCharacter !== char;
                }
            );
    
            this.setState({
                characters: updatedCharactersList,
            });
        }
    }
    
    class Character extends Component {
        render() {
            return (
                <li>
                    <div className="character">
                        <span className="character-name">{this.props.char}</span>
                        <span
                            className="character-delete"
                            onClick={this.handleDelete}
                            > x </span>
                    </div>
                </li>
            )
        };
    
        handleDelete() {
            this.props.onDelete(this.props.char);
        }
    }
    
    
    export default App;
    
  • MehranTM
    MehranTM about 4 years
    This is the most complete answer.