Voulez-vous demander une fonctionnalité ou signaler un bogue ?
Punaise!
Quel est le comportement actuel?
Lors du rendu d'un élément input
de type checkbox
avec un gestionnaire onClick
et onChange
, onChange
est toujours appelé même si event.preventDefault()
est appelé dans le gestionnaire onClick
.
Si le comportement actuel est un bogue, veuillez fournir les étapes pour reproduire et si possible une démo minimale du problème via https://jsfiddle.net ou similaire (template: https://jsfiddle.net/reactjs/69z2wepo/).
Quel est le comportement attendu?
L'appel de event.preventDefault
dans le gestionnaire onClick
devrait empêcher l'action par défaut de se produire (ou annuler son effet), qui consiste à mettre à jour la valeur de l'élément input
. Cela devrait empêcher tout écouteur d'événement change
d'être appelé. Voir https://jsfiddle.net/L1eskzsq/ pour le comportement attendu
Quelles versions de React et quel navigateur / système d'exploitation sont concernés par ce problème?
Testé à l'aide d'une version de master, macOS 10.12.2, vérifié dans:
Safari 10.0.2 appelle l'écouteur d'événement change
dans les deux cas.
L'événement change
est créé par le ChangeEventPlugin
en réponse à topChange
, avant même que votre gestionnaire onClick
soit appelé (donc avant le preventDefault
call), ici: https://github.com/facebook/react/blob/master/src/renderers/dom/shared/eventPlugins/ChangeEventPlugin.js#L338
Ce plugin essaie de déterminer si la valeur de la case à cocher changera, et si oui, il mettra en file d'attente l'événement change
. Il utilise la fonctionnalité inputValueTracking
pour comparer la valeur précédente avec la valeur suivante pour prendre cette décision, ici: https://github.com/facebook/react/blob/master/src/renderers/dom/shared/ inputValueTracking.js # L154
Le nextValue
est pris directement du DOM comme ceci: value = isCheckable(node) ? '' + node.checked : node.value;
https://github.com/facebook/react/blob/master/src/renderers/dom/shared/inputValueTracking.js# L56
Le problème est que la node.checked
est temporairement vraie à ce stade, donc après le premier clic, c'est:
lastValue == false
(correct)
nextValue == true
(incorrect)
et au prochain clic sur la case à cocher, l'événement change
n'est plus déclenché, car les valeurs sont:
lastValue == true
(incorrect)
nextValue == true
(incorrect)
Dans l'ensemble, le problème semble être que React demande au DOM quelle est la checked
du nœud, avant que le preventDefault dans onClick
puisse être appelé.
Merci @karelskopek et @Mintaffee pour votre aide dans ce domaine.
@aweary Pensez-vous que cela vaut la peine d'être résolu?
Pas de progrès avec celui-ci? J'utilise React v16.2 et le comportement est le même - onChange est déclenché la première fois et les clics suivants ne le déclenchent pas. Je dirais ici une question très prioritaire.
Est-ce lié? preventDefault
appelé dans une case onChange
cocher https://codesandbox.io/s/0qpr936xkv
Encore un problème dans React 16.8.6. Une solution de contournement possible consiste à tout faire dans le gestionnaire 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`"
}
}
Si vous utilisez un composant contrôlé, une autre option que j'ai trouvée est de simplement laisser la valeur dans l'état inchangé:
onChange(event) {
// Check something to prevent radio change
if (...) {
return;
}
// Sync value in the store
this.props.setDateTime(event.target.value);
}
Pour une solution plus simple, vous pouvez exécuter l'événement e.preventDefault sur onMouseDown pour empêcher onChange de se déclencher.
onMouseDown(e) {
if (...) {
e.preventDefault();
return;
}
}
Commentaire le plus utile
Est-ce lié?
preventDefault
appelé dans une caseonChange
cocher https://codesandbox.io/s/0qpr936xkv