简体   繁体   中英

What is the reason that function is not passed to a Button

I'm aware of JavaScript's scopes but probably I don't understand them fully because this code doesn't work.

This code uses React and Relay Modern frameworks.

There are 2 buttons, first one inside queryRender which is passed into Relay Modern QueryRenderer and second one afterwards (see function render ). The second one is working, first one doesn't execute the clickTest function. (This is simplified version of actual code)

class Candidates extends Component {
  static propTypes = {
    viewer: PropTypes.object
  }

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

  clickTest () {
    console.log('click works')
  }    

  queryRender ({error, props}) {
    if (error) {
      return <pre>{error.message}</pre>
    } else if (props) {
      return (
        <div>
          <Button onClick={this.clickTest}>this DOESN'T work</Button>
        </div>
      )
    }
    return <Loader active>Loading...</Loader>
  }

  render () {
    return (
      <div>
        <QueryRenderer
          environment={environment}
          query={query} 
          render={this.queryRender}
        />
        <Button onClick={this.clickTest}>this works</Button>
      </div>
    )
  }
}

The query variable is defined, I just didn't include it in that excerpt.

When I substitue first button's onClick function with an anonymous one

<Button onClick={() => this.clickTest()}>this DOESN'T work</Button>

then I get such error: Uncaught TypeError: _this2.clickTest is not a function

Can anyone explain to me why this code behaves the way it does?

In javascript, the meaning of this isn't determined when a function is created , but rather when it is invoked . When QueryRenderer invokes your queryRender function, it doesn't know that it needs to invoke it in the context of your class, so this will not be referring to what you think it's referring to.

You'll either need to bind your queryRender function, much like you're doing with your clicktest function in the constructor, or you'll need to redesign queryRender so it doesn't need a reference to this .

To expand upon both Artur and Nicholas' answers, you either need to bind() this or use an arrow function to make sure that this is referring to the component itself. You already have the bind method down, here's en example of the arrow function which gets rid of the need to bind because arrow functions don't actually bind a this value, they use their parents scope instead...

class Candidates extends Component {
  static propTypes = {
    viewer: PropTypes.object
  }

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

  clickTest () {
    console.log('click works')
  }    

  queryRender = ({error, props}) => {
    if (error) {
      return <pre>{error.message}</pre>
    } else if (props) {
      return (
        <div>
          <Button onClick={this.clickTest}>this DOESN'T work</Button>
        </div>
      )
    }
    return <Loader active>Loading...</Loader>
  }

  render () {
    return (
      <div>
        <QueryRenderer
          environment={environment}
          query={query} 
          render={this.queryRender}
        />
        <Button onClick={this.clickTest}>this works</Button>
      </div>
    )
  }
}

Arrow function doesn't create new scope and its scope is enclosing execution context, in this case it's QueryRenderer scope where you don't have this function. When you pass it as simple function then the scope will be undefined or not, I don't know what Button does inside. I haven't used Rely and not sure you can refer to component from Rely render method.

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