最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - React onChange on radio button fires onClick on parent div element - Stack Overflow

programmeradmin7浏览0评论

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>

发布评论

评论列表(0)

  1. 暂无评论