Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React onChange on radio button fires onClick on parent div element

I want to handle changes by keyboard differently from changes by mouse clicks. For this purpose I have created following component. 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.com/ajax/libs/react/15.0.2/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.2/react-dom.min.js"></script>

1 Answers

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.com/ajax/libs/react/15.0.2/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.2/react-dom.min.js"></script>
like image 111
Chris Avatar answered Jan 21 '26 08:01

Chris