6

My field onClick event toggles a dropdown, the onFocus event opens it.
When the onFocus event is fired the onClick event is fired afterwards and closes the newly opened dropdown. How can I prevent firing on Click in Case onFocus fired?

preventDefault and stopPropagation do not work, both events are always fired

<TextInputV2
  label={label}
  onChange={handleInputOnChange}
  onClick={handleOnClick}
  onFocus={handleOnFocus}
  onKeyUp={handleInputOnKeyUp}
  readOnly={!searchable}
  value={inputValue}
/>

.......

  const handleOnFocus = (event: React.FocusEvent): void => {
    if (!isOpen) {
      changeIsOpen(true)
    }
  }

  const handleOnClick = (event: React.SyntheticEvent): void => {
    if (!searchable) {
      toggleOpen()
    }
  }
Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
jeff
  • 1,169
  • 1
  • 19
  • 44
  • is there any codepen available? – Harish Sep 02 '19 at 11:29
  • The issue is that you don't stop event to be propagated. In case of the click both onClick and onFocus will be fired if you will not stop event propagation. See: https://stackoverflow.com/questions/282245/dom-event-precedence – Maciej Trojniarz Sep 02 '19 at 11:32

2 Answers2

15

You will want to change onClick to onMouseDown. Since event order is

  • mousedown
  • change (on focused input)
  • blur (on focused element)
  • focus
  • mouseup
  • click
  • dblclick

from: this answer

You want to preventDefault/stoPropagation BEFORE the focus event, which means you have to use "onMouseDown" to properly stop it before the focus event get triggered.

In your case it would be:

<TextInputV2
  label={label}
  onChange={handleInputOnChange}
  onMouseDown={handleOnClick}
  onFocus={handleOnFocus}
  onKeyUp={handleInputOnKeyUp}
  readOnly={!searchable}
  value={inputValue}
/>
 const handleOnClick = (event) => {
    event.preventDefault()
    event.stopPropagation()
  if (!searchable) {
   toggleOpen()
  }
}
JCQuintas
  • 1,060
  • 1
  • 8
  • 18
0

https://jsfiddle.net/reactjs/69z2wepo/

class Hello extends React.Component {
    constructor(props) {
    super(props);
    this.state = {
        text: 'hello'
    }

    this.lastFocus = 0;
  }

  handleOnClick(ev) {
    const now = new Date().getTime();
    console.log('diff since lastFocus');
    console.log(now - this.lastFocus);
    if (now - this.lastFocus < 200) {
        return;
    }
    const newText = this.state.text + 'c';
    this.setState({text:newText})
  }

  handleOnFocus(ev) {
    this.lastFocus = new Date().getTime();

    const newText = this.state.text + 'f';
    this.setState({text:newText});
  }

  render() {
    return <div>
      <input name="" id="" cols="30" rows="10"
        value={this.state.text}

         onClick={this.handleOnClick.bind(this)}
          onFocus={this.handleOnFocus.bind(this)}
      ></input>
    </div>;
  }
}

ReactDOM.render(
  <Hello name="World" />,
  document.getElementById('container')
);

You store the time of your lastFocus -- not in this.state because that updates asynchronously and you cannot rely on that being updated in the onClick handler by calling setState in the onFocus handler. You put it directly on the instance and update it directly.

You can just use a rule of thumb that says if the last focus was within 200ms, then this onClick handler is from the same event as the onFocus handler, and therefore not run the rest of your onClick handler.

My fiddle is not obviously your entire use case, I'm just adding f on focus and c on click to the input text.

TKoL
  • 13,158
  • 3
  • 39
  • 73