React: event.preventDefault в обработчике кликов не предотвращает вызов onChange

Созданный на 17 февр. 2017  ·  6Комментарии  ·  Источник: facebook/react

Вы хотите запросить функцию или сообщить об ошибке ?

Ошибка!

Каково текущее поведение?

При рендеринге элемента input типа checkbox с обработчиком onClick и onChange , onChange по-прежнему вызывается, даже если event.preventDefault() вызывается в обработчике onClick .

Если текущее поведение является ошибкой, предоставьте шаги для воспроизведения и, если возможно, минимальную демонстрацию проблемы через https://jsfiddle.net или аналогичный (шаблон: https://jsfiddle.net/reactjs/69z2wepo/).

http://jsfiddle.net/rf3w7apc/

Какое ожидаемое поведение?

Вызов event.preventDefault в обработчике onClick должен предотвратить выполнение действия по умолчанию (или отменить его действие), которое заключается в обновлении значения элемента input . Это должно остановить вызов любого прослушивателя событий change . См. Https://jsfiddle.net/L1eskzsq/ для ожидаемого поведения

Какие версии React и какой браузер / ОС подвержены этой проблеме?

Протестировано с использованием сборки из master, macOS 10.12.2, проверено в:

  • Chrome 56.0.2924.87 (64-разрядная версия)
  • Firefox 51.0.1 (64-разрядная версия)

Safari 10.0.2 вызывает прослушиватель событий change в обоих случаях.

Самый полезный комментарий

Это связано? preventDefault вызывается в поле флажка onChange вызывает рассинхронизацию React и состояния браузера: https://codesandbox.io/s/0qpr936xkv

Все 6 Комментарий

Событие change создается ChangeEventPlugin в качестве ответа на topChange , прежде чем ваш обработчик onClick даже будет вызван (так до preventDefault call), здесь: https://github.com/facebook/react/blob/master/src/renderers/dom/shared/eventPlugins/ChangeEventPlugin.js#L338

Этот плагин пытается выяснить, изменится ли значение флажка, и если да, он поставит в очередь событие change . Он использует функциональность inputValueTracking для сравнения предыдущего значения со следующим значением, чтобы принять это решение, здесь: https://github.com/facebook/react/blob/master/src/renderers/dom/shared/ inputValueTracking.js # L154

nextValue берется непосредственно из DOM следующим образом: value = isCheckable(node) ? '' + node.checked : node.value; https://github.com/facebook/react/blob/master/src/renderers/dom/shared/inputValueTracking.js# L56

Проблема в том, что в этот момент значение node.checked временно истинно, поэтому после первого щелчка оно будет:

lastValue == false (правильно)
nextValue == true (неверно)

и при следующем щелчке по флажку событие change больше не запускается, потому что значения:

lastValue == true (неверно)
nextValue == true (неверно)

В целом проблема, похоже, заключается в том, что React спрашивает DOM, каково значение checked узла, прежде чем можно будет вызвать preventDefault в onClick .

Спасибо @karelskopek и @Mintaffee за вашу помощь в этом.

@aweary Как вы думаете, стоит ли это решать?

Нет прогресса с этим? Я использую React v16.2, и поведение такое же - onChange запускается в первый раз, а последующие щелчки не запускают его. Я бы сказал, здесь достаточно приоритетный вопрос ..

Это связано? preventDefault вызывается в поле флажка onChange вызывает рассинхронизацию React и состояния браузера: https://codesandbox.io/s/0qpr936xkv

По-прежнему проблема в React 16.8.6. Один из возможных обходных путей - сделать все в обработчике onClick :

const radios = ['one', 'two', 'three'];

class Row extends Component {
  constructor(props) {
    super(props);
    this.onClick = this.onClick.bind(this);
    this.onChange = this.onChange.bind(this);
  }

  render() {
    const { value } = this.props;
    return (
      <div className="row">
        <div className="radio-group">
          {radios.map(radio => (
            <label key={radio} className="radio">
              <input
                type="radio"
                name="radio"
                value={radio}
                checked={radio === value}
                onClick={this.onClick}
                onChange={this.onChange}
              /><span>Radio</span>
            </label>
          ))}
        </div>
      </div>
    );
  }

  onClick(event) {
    // Check something to prevent radio change
    if (...) {
      event.preventDefault();
      return;
    }
    // Sync value in the store
    this.props.setValue(event.target.value);
  }
  onChange() {
    // We need this empty handler to suppress React warning:
    // "You provided a `checked` prop to a form field without an `onChange` handler.
    // This will render a read-only field. If the field should be mutable use `defaultChecked`.
    // Otherwise, set either `onChange` or `readOnly`"
  }
}

Если вы используете управляемый компонент, я нашел еще один вариант - просто оставить значение в неизменном состоянии:

onChange(event) {
  // Check something to prevent radio change
  if (...) {
    return;
  }
  // Sync value in the store
  this.props.setDateTime(event.target.value);
}

Для более простого решения вы можете выполнить e.preventDefault для события onMouseDown, чтобы предотвратить запуск onChange.

onMouseDown(e) { if (...) { e.preventDefault(); return; } }

Была ли эта страница полезной?
0 / 5 - 0 рейтинги