简体   繁体   中英

Getting a typeError in React saying this.props is not a function when child component tries to call passed function

When I try to call the passed function from the child function, I get the following error

Uncaught TypeError: this.props.addHours is not a function

Here's a codepen with the issue: example

Here's the code with my components:

class Application extends React.Component {
  constructor(props) {
    super(props);

    this.handleSubmit = this.handleSubmit.bind(this);
    this.addHours = this.addHours.bind(this)

    this.state = {
      flies:[{
      name: 'Elk Hair Caddis',
      hours: 10,
      fish: 12
      },
      {
      name: 'Adams',
      hours: 6,
      fish: 4
      }
      ]
    };
  }

  handleSubmit(event) {
    alert('A fly was submitted');
    event.preventDefault();

    let subName = document.getElementById("subName").value
    let subHours = document.getElementById("subHours").value
    let subFish = document.getElementById("subFish").value

    document.forms[0].reset()

   function flyMaker(name, hours, fish) {
      this.name = name
      this.hours = hours
      this.fish = fish
    }

    let myFly = new flyMaker(subName, subHours, subFish)

    let tempState = this.state.flies
    tempState.push(myFly)
    this.setState(tempState)
  }

 addHours(e){
    e.preventDefault()
    alert('hey')
    console.log('hey')
  }

  render() {
    return <div>
      <h1>Fly List</h1>
         <ul>
          {this.state.flies.map(function(fly){
            return <li><Fly addHours={this.addHours} name={fly.name} hours={fly.hours} fish={fly.fish} /></li>;
          })}
        </ul>
      <div id='addFly'>
        <h1>Add a Fly</h1>
        <form onSubmit={this.handleSubmit}>
        <p>Name:</p>
        <input id='subName' type='text'/>
        <p>Hours:</p>
        <input id='subHours' type='text'/>
         <p>Fish Caught:</p>
        <input id='subFish' type='text'/>
          <br/>
        <input type='submit' value='submit' />
        </form>
      </div>
    </div>;
  }
}

class Fly extends React.Component { 
  constructor(props) {
    super(props);
    this.doAddHours = this.doAddHours.bind(this)

  }

  doAddHours() {
    this.props.addHours()
  }

  render() {
    return <div>
      <p>{this.props.name}</p>
      <div>Hours fished: {this.props.hours}</div>
      <div className='increment' onClick={this.doAddHours}>+</div><div className='increment'>-</div>
      <div>Fish Caught: {this.props.fish}</div>
      <div className='increment'>+</div><div className='increment'>-</div>
    </div>;
  }
}

Basically, I'm passing the child component a function so I'm not sure why it doesn't think the prop is one. I'm pretty sure I've bond everything correctly, which was my first guess, but perhaps not. It would be greatly appreciated if someone could point out what I'm doing wrong!

you do not use an arrow function in this.state.flies.map so it does not have the scope needed to get the context for this

{this.state.flies.map( fly => {
        return <li><Fly addHours={this.addHours} name={fly.name} hours={fly.hours} fish={fly.fish} /></li>;
      })}

This is happening because you are using this inside a function() {} . which means the addHours function is not in scope for the available this . Depending on if you are compiling this or not you can do one of the following:

If you are open to using an arrow function:

{
  this.state.flies.map(fly => {
    return (
      <li>
        <Fly
          addHours={this.addHours}
          name={fly.name}
          hours={fly.hours}
          fish={fly.fish}
        />
      </li>;
  })
}

if you want to continue using function() {} :

// At the top of the render function somewhere
var _this = this;

// In your return
{
  this.state.flies.map(function(fly) {
    return (
      <li>
        <Fly
          addHours={_this.addHours}
          name={fly.name}
          hours={fly.hours}
          fish={fly.fish}
        />
      </li>;
  })
}

The this inside your this.state.flies.map is not the one you are expected. You should defined varibable before calling map to reference the correct this:

render() {

const _this = this;

return <div>
  <h1>Fly List</h1>
     <ul>
      {this.state.flies.map(function(fly){
        return <li><Fly addHours={_this.addHours} name={fly.name} hours={fly.hours} fish={fly.fish} /></li>;
      })}
    </ul>
...

You have 2 main issues here:

  1. The map function is shadowing the this context. You can extract it to an external method and bind it as well in the constructor
    Or use an arrow function so it will use a lexical context for the this :

     this.state.flies.map((fly) => { return <li><Fly addHours={this.addHours} name={fly.name} hours={fly.hours} fish={fly.fish} /></li>; })} 
  2. Another issue (error) you will face later is that you are not passing the event ( e ) from the Fly method to the parent:

      doAddHours(e) { this.props.addHours(e) } 


    Here is a running example of your code:

 class Application extends React.Component { constructor(props) { super(props); this.handleSubmit = this.handleSubmit.bind(this); //this.addHours = this.addHours.bind(this) this.state = { flies: [{ name: 'Elk Hair Caddis', hours: 10, fish: 12 }, { name: 'Adams', hours: 6, fish: 4 } ] }; } handleSubmit(event) { //alert('A fly was submitted'); event.preventDefault(); let subName = document.getElementById("subName").value let subHours = document.getElementById("subHours").value let subFish = document.getElementById("subFish").value document.forms[0].reset() function flyMaker(name, hours, fish) { this.name = name this.hours = hours this.fish = fish } let myFly = new flyMaker(subName, subHours, subFish) let tempState = this.state.flies tempState.push(myFly) this.setState(tempState) } addHours = (e) => { e.preventDefault() //alert('hey') console.log('hey') } render() { return <div> <h1>Fly List</h1> <ul> {this.state.flies.map((fly) => { return <li><Fly addHours={this.addHours} name={fly.name} hours={fly.hours} fish={fly.fish} /></li>; })} </ul> <div id='addFly'> <h1>Add a Fly</h1> <form onSubmit={this.handleSubmit}> <p>Name:</p> <input id='subName' type='text' /> <p>Hours:</p> <input id='subHours' type='text' /> <p>Fish Caught:</p> <input id='subFish' type='text' /> <br /> <input type='submit' value='submit' /> </form> </div> </div>; } } class Fly extends React.Component { constructor(props) { super(props); this.doAddHours = this.doAddHours.bind(this) } doAddHours(e) { this.props.addHours(e) } render() { return <div> <p>{this.props.name}</p> <div>Hours fished: {this.props.hours}</div> <div className='increment' onClick={this.doAddHours}>+</div><div className='increment'>-</div> <div>Fish Caught: {this.props.fish}</div> <div className='increment'>+</div><div className='increment'>-</div> </div>; } } /* * Render the above component into the div#app */ ReactDOM.render(<Application />, document.getElementById('app')); 
 html, body { height: 100%; } body { background: #333; display: -webkit-box; display: -ms-flexbox; display: flex; -webkit-box-pack: center; -ms-flex-pack: center; justify-content: center; font-family: Helvetica Neue; } h1 { font-size: 2em; color: #eee; display: inline-block; } a { color: white; } p { margin-top: 1em; text-align: center; color: #aaa; } .increment { display: inline-block; padding: 10px; background-color: black; color: white; margin: 4px; } 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script> <div id="app"></div> 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM