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/).
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:
O Safari 10.0.2 chama o ouvinte de evento change
em ambos os casos.
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;
}
}
Comentários muito úteis
Isso está relacionado?
preventDefault
chamado em uma caixa de seleçãoonChange
faz com que o React e o estado do navegador saiam de sincronia: https://codesandbox.io/s/0qpr936xkv