admin管理员组

文章数量:1327826

I want to handle changes by keyboard differently from changes by mouse clicks. For this purpose I have created following ponent. My problem is, that when I use the arrow keys and space to select the radio button, it uses the onClick method from the parent div element before it uses the onChange method from the input element. What is the reason for this behaviour and how can I handle it correctly?

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

    this.onChange = this.onChange.bind(this);
    this.onClick = this.onClick.bind(this);

    this.state = { checked: false };
  }

  onChange() {
    this.setState({ checked: !this.state.checked });
    console.log('onChange');
  }

  onClick() {
    this.onChange();
    console.log('onClick');
  }

  render() {
    return (
      <div onClick={this.onClick}>
        <input
          type="radio"
          checked={this.state.checked}
          onChange={this.onChange}
        />
        Click me!
      </div>
    );
  }
}

ReactDOM.render(<Radio />, document.getElementById('root'));
<div id="root"></div>
<script src=".0.2/react.min.js"></script>
<script src=".0.2/react-dom.min.js"></script>

I want to handle changes by keyboard differently from changes by mouse clicks. For this purpose I have created following ponent. My problem is, that when I use the arrow keys and space to select the radio button, it uses the onClick method from the parent div element before it uses the onChange method from the input element. What is the reason for this behaviour and how can I handle it correctly?

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

    this.onChange = this.onChange.bind(this);
    this.onClick = this.onClick.bind(this);

    this.state = { checked: false };
  }

  onChange() {
    this.setState({ checked: !this.state.checked });
    console.log('onChange');
  }

  onClick() {
    this.onChange();
    console.log('onClick');
  }

  render() {
    return (
      <div onClick={this.onClick}>
        <input
          type="radio"
          checked={this.state.checked}
          onChange={this.onChange}
        />
        Click me!
      </div>
    );
  }
}

ReactDOM.render(<Radio />, document.getElementById('root'));
<div id="root"></div>
<script src="https://cdnjs.cloudflare./ajax/libs/react/15.0.2/react.min.js"></script>
<script src="https://cdnjs.cloudflare./ajax/libs/react/15.0.2/react-dom.min.js"></script>

Share Improve this question edited Jun 2, 2017 at 8:54 asked Jun 2, 2017 at 8:47 user5520186user5520186
Add a ment  | 

3 Answers 3

Reset to default 4

As seen in this issue and this part of the W3C documentation, this seems to be a default browser behavior.

When the user triggers an element with a defined activation behavior in a manner other than clicking it, the default action of the interaction event must be to run synthetic click activation steps on the element.

To work around this, add the following logic in your onClick handler:

if (e.type === 'click' && e.clientX !== 0 && e.clientY !== 0) {
  // This is a real click. Do something here
}

Creds to GitHub user Ro Savage, for pointing this out.


Here's a full demo for you:

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

    this.onChange = this.onChange.bind(this);
    this.onClick = this.onClick.bind(this);

    this.state = { checked: false };
  }

  onChange() {
    this.setState({ checked: !this.state.checked });
    console.log('onChange');
  }

  onClick(e) {
    if (e.type === 'click' && e.clientX !== 0 && e.clientY !== 0) {
      this.onChange();
      console.log('onClick');
    } else {
      console.log('prevented "onClick" on keypress');
    }
  }

  render() {
    return (
      <div onClick={this.onClick}>
        <input
          type="radio"
          checked={this.state.checked}
          onChange={this.onChange}
        />
        Click me!
      </div>
    );
  }
}

ReactDOM.render(<Radio />, document.getElementById('root'));
<div id="root"></div>
<script src="https://cdnjs.cloudflare./ajax/libs/react/15.0.2/react.min.js"></script>
<script src="https://cdnjs.cloudflare./ajax/libs/react/15.0.2/react-dom.min.js"></script>

You can disable the propagation of the click event:

<input
    type="radio"
    checked={this.state.checked}
    onClick={(e)=>{e.stopPropagation()}}
    onChange={this.onChange}
/>

The order of events matters: the click event will fire before the change event. Because events bubble up, the onClick-handler of the parent elements will get triggered before the onChange-handler of the child-element.

To fix your specific issue, you could for example listen to the keydown-event on the container-div. This event should get triggered before click- or change-events.

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

    this.onChange = this.onChange.bind(this);
    this.onKeyUp = this.onKeyUp.bind(this);

    this.state = { checked: false };
  }

  onChange() {
    this.setState({ checked: !this.state.checked });
    console.log('onChange');
  }

  onKeyUp() {
    console.log('onKeyUp');
    this.onChange();
  }

  render() {
    return (
      <div onKeyUp={this.onKeyUp}>
        <input
          type="radio"
          checked={this.state.checked}
          onChange={this.onChange}
        />
        Click me!
      </div>
    );
  }
}

ReactDOM.render(<Radio />, document.getElementById('root'));
<div id="root"></div>
<script src="https://cdnjs.cloudflare./ajax/libs/react/15.0.2/react.min.js"></script>
<script src="https://cdnjs.cloudflare./ajax/libs/react/15.0.2/react-dom.min.js"></script>

本文标签: javascriptReact onChange on radio button fires onClick on parent div elementStack Overflow