React: event.preventDefault no manipulador de cliques não impede que onChange seja chamado

Criado em 17 fev. 2017  ·  6Comentários  ·  Fonte: facebook/react

Você quer solicitar um recurso ou relatar um bug ?

Erro!

Qual é o comportamento atual?

Ao renderizar um elemento input do tipo checkbox com um manipulador onClick e onChange , onChange ainda é chamado, embora event.preventDefault() é chamado no manipulador onClick .

Se o comportamento atual for um bug, forneça as etapas para reproduzir e, se possível, uma demonstração mínima do problema via https://jsfiddle.net ou semelhante (modelo: https://jsfiddle.net/reactjs/69z2wepo/).

http://jsfiddle.net/rf3w7apc/

Qual é o comportamento esperado?

Chamar event.preventDefault no manipulador onClick deve evitar que a ação padrão ocorra (ou desfazer seu efeito), que é atualizar o valor do elemento input . Isso deve impedir que qualquer ouvinte de change event seja invocado. Veja https://jsfiddle.net/L1eskzsq/ para o comportamento esperado

Quais versões do React e quais navegadores / sistemas operacionais são afetados por esse problema?

Testado usando uma compilação do mestre, macOS 10.12.2, verificado em:

  • Chrome 56.0.2924.87 (64 bits)
  • Firefox 51.0.1 (64 bits)

O Safari 10.0.2 chama o ouvinte de evento change em ambos os casos.

DOM Bug

Comentários muito úteis

Isso está relacionado? preventDefault chamado em uma caixa de seleção onChange faz com que o React e o estado do navegador saiam de sincronia: https://codesandbox.io/s/0qpr936xkv

Todos 6 comentários

O evento change está sendo criado pelo ChangeEventPlugin como uma resposta a topChange , antes mesmo de seu onClick manipulador ser chamado (portanto, antes de preventDefault call), aqui: https://github.com/facebook/react/blob/master/src/renderers/dom/shared/eventPlugins/ChangeEventPlugin.js#L338

Este plugin está tentando descobrir se o valor da caixa de seleção mudará e, se sim, ele enfileirará o evento change . Ele usa a funcionalidade inputValueTracking para comparar o valor anterior com o próximo valor para tomar essa decisão, aqui: https://github.com/facebook/react/blob/master/src/renderers/dom/shared/ inputValueTracking.js # L154

O nextValue é retirado diretamente do DOM desta forma: value = isCheckable(node) ? '' + node.checked : node.value; https://github.com/facebook/react/blob/master/src/renderers/dom/shared/inputValueTracking.js# L56

O problema é que o node.checked é temporariamente verdadeiro neste ponto, portanto, após o primeiro clique, ele é:

lastValue == false (correto)
nextValue == true (incorreto)

e no próximo clique na caixa de seleção, o evento change não está mais sendo disparado, pois os valores são:

lastValue == true (incorreto)
nextValue == true (incorreto)

De modo geral, o problema parece ser que o React está perguntando ao DOM qual é o checked do nó, antes que o preventDefault em onClick possa ser chamado.

Obrigado @karelskopek e @Mintaffee por sua ajuda em investigar isso.

@aweary Você acha que vale a pena resolver isso?

Nenhum progresso com este? Estou usando o React v16.2 e o comportamento é o mesmo - onChange é acionado na primeira vez e os cliques subsequentes não o acionam. Eu diria que é uma questão de alta prioridade aqui.

Isso está relacionado? preventDefault chamado em uma caixa de seleção onChange faz com que o React e o estado do navegador saiam de sincronia: https://codesandbox.io/s/0qpr936xkv

Ainda é um problema no React 16.8.6. Uma solução possível é fazer tudo em onClick handler:

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`"
  }
}

Se você usar um componente controlado, outra opção que encontrei é simplesmente deixar o valor no estado inalterado:

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

Para uma solução mais simples, você pode fazer e.preventDefault no evento onMouseDown para evitar que onChange seja acionado.

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

Esta página foi útil?
0 / 5 - 0 avaliações