Albert Nemec Albert Nemec - 3 months ago 12
Javascript Question

React.js - Can't read property of undefined

I'm making very simple react app. Yet as I try to invoke method of parent (actually grandparent) component via onChange event, I keep getting

Uncaught TypeError: Cannot read property 'props' of undefined
.

Here is the component/form that is triggering the event (thus invoking method on binded parent component... Yes I used .bound(this) on the method as I passed it down from parent component via props.).

class MonthsTable extends Component {
handleChangeOnMonth(e){
this.props.setMonth(e.target.id, e.target.value); // here the method is not found, causing error.
}
render(){
console.log(this.props.setMonth) // here the method is present alright
return (<form>
{this.props.months.map((e, i) =>
<input
type='number'
id={i}
key={i} // yes I know, bad habit, but in this case it doesn't matter
value={this.props.months[i]}
onChange={this.handleChangeOnMonth} />)}
</form>)
}
}


Here is how I pass the method as props from most parent (grandparent) component.

<Months setMonth={this.setMonth.bind(this)} />


Here is how I pass the method as props in the parent (the component that is between method owner and method invoker)

<MonthsTable setMonth={this.props.setMonth} />


And finally passed to component (MonthsTable) that you saw first. Wheter it is relevant or not, final (most child) components is displayed depending of if statement which works just fine (Might somehow be relevant, I don't know).

Question is... Why is the (setMonth) method 'invisible' inside of (handleChangeOnMonth) method.

Thanks for any advice.

Answer

The actual problem here is that the this context is not defined in your handleChangeOnMonth function. This is caused because of the way that javascript handles the contexts of functions, basically when calling functions if you are not calling them directly from the object, and they are not bound they will not have a defined context, and since you are passing the function as a parameter to the input component, it loses the context.

The simplest way to fix this is to bind the function, I suggest that you bind the function in the constructor, like so:

class MonthsTable extends Component {
  constructor(props, context){
    super(props, context);
    this.handleChangeOnMonth = this.handleChangeOnMonth.bind(this);
  }
  handleChangeOnMonth(e){ 
    this.props.setMonth(e.target.id, e.target.value);
  }
  render(){
    return (<form>
      {this.props.months.map((e, i) =>
         <input
          type='number'
          id={i} 
          key={i} 
          value={this.props.months[i]}
          onChange={this.handleChangeOnMonth} />)}
    </form>)
  }
}

alternatively if you are using decorators you could use the core-decorators package to do this in a more elegant way:

import {autobind} from "core-decorators"

@autobind
class MonthsTable extends Component {     
  handleChangeOnMonth(e){ 
    this.props.setMonth(e.target.id, e.target.value);
  }
  render(){
    return (<form>
      {this.props.months.map((e, i) =>
         <input
          type='number'
          id={i} 
          key={i} 
          value={this.props.months[i]}
          onChange={this.handleChangeOnMonth} />)}
    </form>)
  }
}