TLDR: The specific problem in your cod 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:
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 atribute 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 this 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>
)
};
}