Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React 'cannot read property of undefined' when using map

I'm making a very basic React app from teamtreehouse.com, and I'm constantly encountering

"TypeError: Cannot read property 'onPlayerScoreChange' of undefined"

even though I'm binding my functions correctly (I think)

'onPlayerScoreChange' Is a method in the Grandparent component which executes when a user hits a '+' or '-' button to change a player's score.

It would be really helpful if someone could explain what is wrong, because I think I am setting this.onPlayerScoreChange = this.onPlayerScoreChange.bind(this) in the great grandparent's constructor.

Parent component:

class App extends React.Component {
constructor(props) {
    super(props);
    this.onPlayerScoreChange = this.onPlayerScoreChange.bind(this)
    this.state = {
        initialPlayers: props.initialPlayers,
    };
}

onPlayerScoreChange(delta, index) {
    this.setState((prevState, props) => {
        return {initialPlayers: this.prevState.initialPlayers[index].score += delta}
    })
}

render() {
    return(
        <div className = "scoreboard">
            <Header title = {this.props.title}/>
            <div className = "players">
                {this.state.initialPlayers.map(function(player, index) {
                    return(
                        <Player 
                        name = {player.name} 
                        score = {player.score} 
                        key = {player.id} 
                        index = {index}
                        onScoreChange = {this.onPlayerScoreChange}
                        />
                    )
                })}
            </div>
        </div>
    )
}}

(Component has default props for title)

Child component:

class Player extends React.Component {
render() {
    return(
        <div className = "player">
            <div className = "player-name">
                {this.props.name}
            </div>
            <div className = "player-score">
                <Counter score = {this.props.score} onChange = {this.props.onScoreChange} index = {this.props.index}/>
            </div>
        </div>
)
}}

Grandchild component:

class Counter extends React.Component {
constructor(props) {
    super(props)
    this.handleDecrement = this.handleDecrement.bind(this)
    this.handleIncrement = this.handleIncrement.bind(this)
}

handleDecrement() {
    this.props.onChange(-1, this.props.index)
}

handleIncrement() {
    this.props.onChange(1, this.props.index)
}

render() {
    return(
        <div className = "counter">
            <button className = "counter-action decrement" onClick = {this.handleDecrement}> - </button>
            <div className = "counter-score"> {this.props.score} </div>
            <button className = "counter-action increment" onClick = {this.handleIncrement}> + </button>
        </div>
)}}

Thank you!

like image 918
VWang Avatar asked Jul 10 '17 11:07

VWang


2 Answers

You have not done binding for the map function where you are using onScoreChange = {this.onPlayerScoreChange},

you can either use bind or arrow functions for binding

P.S. Binding is needed because the context of the map function is different from the React Component context and hence this inside this function won't be Referring to the React Components this and thus you can't access that properties of the React Component class.

With Arrow function:

 {this.state.initialPlayers.map((player, index)=> {
                return(
                    <Player 
                    name = {player.name} 
                    score = {player.score} 
                    key = {player.id} 
                    index = {index}
                    onScoreChange = {this.onPlayerScoreChange}
                    />
                )
            })}

With bind

   {this.state.initialPlayers.map(function(player, index) {
                return(
                    <Player 
                    name = {player.name} 
                    score = {player.score} 
                    key = {player.id} 
                    index = {index}
                    onScoreChange = {this.onPlayerScoreChange}
                    />
                )
            }.bind(this))}
like image 127
Shubham Khatri Avatar answered Oct 21 '22 23:10

Shubham Khatri


Can also be done by passing second argument as this to map function as onClick event uses local this of map function which is undefined here and this currently refers to the global object.

{this.state.initialPlayers.map(function(player, index) {
                    return(
                        <Player 
                        name = {player.name} 
                        score = {player.score} 
                        key = {player.id} 
                        index = {index}
                        onScoreChange = {this.onPlayerScoreChange}
                        />
                    )
                }),this}
like image 34
Vikram Saini Avatar answered Oct 22 '22 00:10

Vikram Saini