¿Cuál es la mejor manera de activar el evento onchange en react js?

112

Usamos el paquete Backbone + ReactJS para crear una aplicación del lado del cliente. Confiando en gran medida en notorio valueLink, propagamos valores directamente al modelo a través de su propio contenedor que admite la interfaz ReactJS para el enlace bidireccional.

Ahora nos enfrentamos al problema:

Tenemos un jquery.mask.jscomplemento que formatea el valor de entrada mediante programación, por lo que no activa eventos React. Todo esto conduce a una situación en la que el modelo recibe valores sin formato de la entrada del usuario y pierde los formateados del complemento.

Parece que React tiene muchas estrategias de manejo de eventos según el navegador. ¿Existe alguna forma común de activar un evento de cambio para un elemento DOM en particular para que React lo escuche?

Wallice
fuente
👉 Discusión de React Github: Activar cambio de valor de entrada simulado para React 16
vsync

Respuestas:

174

Para React 16 y React> = 15.6

Setter .value=no funciona como queríamos porque la biblioteca React anula el setter de valores de entrada, pero podemos llamar a la función directamente en el inputcontexto as.

var nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value").set;
nativeInputValueSetter.call(input, 'react 16 value');

var ev2 = new Event('input', { bubbles: true});
input.dispatchEvent(ev2);

Para el elemento de área de texto se debe utilizar prototypede HTMLTextAreaElementclase.

Nuevo ejemplo de codepen .

Todos los créditos a este colaborador y su solución

Respuesta desactualizada solo para React <= 15.5

Con react-dom ^15.6.0puede usar una simulatedbandera en el objeto de evento para que el evento pase

var ev = new Event('input', { bubbles: true});
ev.simulated = true;
element.value = 'Something new';
element.dispatchEvent(ev);

Hice un codepen con un ejemplo

Para entender por qué se necesita una nueva bandera, encontré este comentario muy útil:

La lógica de entrada en React ahora dedupe los eventos de cambio para que no se activen más de una vez por valor. Escucha tanto los eventos onChange / onInput del navegador como los conjuntos en la propuesta de valor del nodo DOM (cuando actualiza el valor a través de javascript). Esto tiene el efecto secundario de significar que si actualiza el valor de la entrada manualmente input.value = 'foo' y luego envía un ChangeEvent con {target: input} React registrará tanto el conjunto como el evento, vea que su valor sigue siendo '' foo ', considérelo un evento duplicado y trágatelo.

Esto funciona bien en casos normales porque un evento "real" iniciado por el navegador no activa conjuntos en element.value. Puede salir de esta lógica en secreto etiquetando el evento que desencadena con una bandera simulada y reaccionar siempre disparará el evento. https://github.com/jquense/react/blob/9a93af4411a8e880bbc05392ccf2b195c97502d1/src/renderers/dom/client/eventPlugins/ChangeEventPlugin.js#L128

Mueca
fuente
1
Gracias, esto funcionó desde react-dom ^ 15.6.0. Pero parece que después de React 16.0 esto ha dejado de funcionar. ¿Alguna idea sobre la alternativa al uso de una bandera simulada para activar los eventos de cambio?
Qwerty
Creo que hay un punto aquí que daría la pista: reactjs.org/blog/2017/09/26/react-v16.0.html#breaking-changes ¿ Alguna idea de qué podría ser?
Qwerty
@Qwerty Actualicé mi respuesta, puede ser que funcione para usted
Grin
¿y qué botón onClick? ¿Qué se debe hacer para desencadenar este tipo de evento?
Bender
@Bender, simplemente llama al método de clic nativo en el elemento
Grin
63

Al menos en las entradas de texto, parece que onChangeestá escuchando eventos de entrada:

var event = new Event('input', { bubbles: true });
element.dispatchEvent(event);
Miguel
fuente
Depende de la versión del navegador. IE8 no admite eventos de entrada. Y ie9 no dispara el evento de entrada cuando eliminas caracteres del cuadro de texto. developer.mozilla.org/en-US/docs/Web/Events/input
wallice
1
IE8 ya no es compatible con React. Para IE9, es posible que pueda usar algo como var event = document.createEvent('CustomEvent'); event.initCustomEvent('input', true, false, { });, pero no tengo una VM IE9 a mano.
Michael
@Michael Estoy probando tu código en IE11 y no funciona en los campos de entrada de reactjs. Funciona con entradas HTML normales. También funciona en Edge. var evt = document.createEvent('CustomEvent'); evt.initCustomEvent('input', true, false, { }); document.getElementById('description').value = 'I changed description'; document.getElementById('description').dispatchEvent(evt);
Bodosko
19

Sé que esta respuesta llega un poco tarde, pero recientemente enfrenté un problema similar. Quería activar un evento en un componente anidado. Tenía una lista con widgets de radio y casillas de verificación (eran divs que se comportaban como casillas de verificación y / o botones de radio) y en algún otro lugar de la aplicación, si alguien cerraba una caja de herramientas, necesitaba desmarcar una.

Encontré una solución bastante simple, no estoy seguro de si esta es la mejor práctica, pero funciona.

var event = new MouseEvent('click', {
 'view': window, 
 'bubbles': true, 
 'cancelable': false
});
var node = document.getElementById('nodeMyComponentsEventIsConnectedTo');
node.dispatchEvent(event);

Esto desencadenó el evento de clic en el domNode y mi controlador adjunto a través de react fue llamado de hecho, por lo que se comporta como esperaría si alguien hiciera clic en el elemento. No he probado onChange pero debería funcionar, y no estoy seguro de cómo funcionará en versiones realmente antiguas de IE, pero creo que MouseEvent es compatible con al menos IE9 y versiones posteriores.

Eventualmente me alejé de esto para mi caso de uso particular porque mi componente era muy pequeño (solo una parte de mi aplicación usaba react ya que todavía lo estoy aprendiendo) y pude lograr lo mismo de otra manera sin obtener referencias a los nodos dom.

ACTUALIZAR:

Como otros han declarado en los comentarios, es mejor usarlo this.refs.refnamepara obtener una referencia a un nodo dom. En este caso, refname es la referencia que adjuntó a su componente a través de <MyComponent ref='refname' />.

Robert-W
fuente
1
En lugar de ID, puede utilizar la React.findDOMNodefunción. goo.gl/RqccrA
m93a
2
> En lugar de ID, puede usar la función React.findDOMNode. O agregue refa su elemento y luego usethis.ref.refName.dispatchEvent
silkAdmin
Es muy probable que el marco haya cambiado desde entonces, es this.ref s .refname
nclord
1
Las referencias de cadena se consideran heredadas ahora y quedarán obsoletas en el futuro. Las referencias de devolución de llamada son el método preferido para realizar un seguimiento del nodo DOM.
dzv3
9

Puede simular eventos usando ReactTestUtils pero eso está diseñado para pruebas unitarias.

Recomendaría no usar valueLink para este caso y simplemente escuchar los eventos de cambio activados por el complemento y actualizar el estado de la entrada en respuesta. El enlace bidireccional se utiliza más como demostración que como cualquier otra cosa; se incluyen en complementos solo para enfatizar el hecho de que el enlace bidireccional puro no es apropiado para la mayoría de las aplicaciones y que generalmente necesita más lógica de aplicación para describir las interacciones en su aplicación.

Sophie Alpert
fuente
Tenía miedo de que la respuesta fuera algo así (. El problema es que React es demasiado estricto con el flujo de trabajo de envío / recepción. En particular, uno debe especificar el controlador onChange para poder reaccionar a la entrada del usuario, de ninguna otra manera excepto el estado no controlado que es amable de over boilerplate para mí.En mi caso, debería declarar este controlador solo para seguir las reglas, mientras que la entrada relevante vendrá del evento onchange desencadenado por jquery. React realmente carece de idea de extender los puntos finales de envío / recepción con el código declarado por el usuario
wallice
1
Y ... en caso de que desee utilizar ReactTestUtils ...ReactTestUtils.Simulate.change(ReactDOM.findDOMNode(this.fieldRef))
colllin
8

Ampliando la respuesta de Grin / Dan Abramov, esto funciona en múltiples tipos de entrada. Probado en React> = 15.5

const inputTypes = [
    window.HTMLInputElement,
    window.HTMLSelectElement,
    window.HTMLTextAreaElement,
];

export const triggerInputChange = (node, value = '') => {

    // only process the change on elements we know have a value setter in their constructor
    if ( inputTypes.indexOf(node.__proto__.constructor) >-1 ) {

        const setValue = Object.getOwnPropertyDescriptor(node.__proto__, 'value').set;
        const event = new Event('input', { bubbles: true });

        setValue.call(node, value);
        node.dispatchEvent(event);

    }

};
cjpete
fuente
5
No funciona para elementos seleccionados. Necesita 'cambiar' en lugar de 'entrada' para el evento.
styks
3

La activación de eventos de cambio en elementos arbitrarios crea dependencias entre componentes sobre las que es difícil razonar. Es mejor seguir con el flujo de datos unidireccional de React.

No hay un fragmento simple para activar el evento de cambio de React. La lógica se implementa en ChangeEventPlugin.js y hay diferentes ramas de código para diferentes tipos de entrada y navegadores. Además, los detalles de implementación varían según las versiones de React.

He creado react-trigger-change que lo hace, pero está destinado a ser utilizado para pruebas, no como una dependencia de producción:

let node;
ReactDOM.render(
  <input
    onChange={() => console.log('changed')}
    ref={(input) => { node = input; }}
  />,
  mountNode
);

reactTriggerChange(node); // 'changed' is logged

CodePen

Vitaly Kuznetsov
fuente
1

bueno, dado que usamos funciones para manejar un evento onchange, podemos hacerlo así:

class Form extends Component {
 constructor(props) {
  super(props);
  this.handlePasswordChange = this.handlePasswordChange.bind(this);
  this.state = { password: '' }
 }

 aForceChange() {
  // something happened and a passwordChange
  // needs to be triggered!!

  // simple, just call the onChange handler
  this.handlePasswordChange('my password');
 }

 handlePasswordChange(value) {
 // do something
 }

 render() {
  return (
   <input type="text" value={this.state.password} onChange={changeEvent => this.handlePasswordChange(changeEvent.target.value)} />
  );
 }
}
cuatro-ojos-04-04
fuente
1

Encontré esto en los problemas de Github de React: funciona como un encanto (v15.6.2)

Así es como lo implementé en una entrada de texto:

changeInputValue = newValue => {

    const e = new Event('input', { bubbles: true })
    const input = document.querySelector('input[name=' + this.props.name + ']')
    console.log('input', input)
    this.setNativeValue(input, newValue)
    input.dispatchEvent(e)
  }

  setNativeValue (element, value) {
    const valueSetter = Object.getOwnPropertyDescriptor(element, 'value').set
    const prototype = Object.getPrototypeOf(element)
    const prototypeValueSetter = Object.getOwnPropertyDescriptor(
      prototype,
      'value'
    ).set

    if (valueSetter && valueSetter !== prototypeValueSetter) {
      prototypeValueSetter.call(element, value)
    } else {
      valueSetter.call(element, value)
    }
  }
a2ron44
fuente
Esto no funciona si el valor del cuadro de texto es el mismo que el valor que está pasando a setNativeValue
Arun
0

Por HTMLSelectElement, es decir<select>

var element = document.getElementById("element-id");
var trigger = Object.getOwnPropertyDescriptor(
  window.HTMLSelectElement.prototype,
  "value"
).set;
trigger.call(element, 4); // 4 is the select option's value we want to set
var event = new Event("change", { bubbles: true });
element.dispatchEvent(event);
octoedro
fuente
-1

Si está utilizando Backbone y React, le recomiendo uno de los siguientes:

Ambos ayudan a integrar modelos y colecciones de Backbone con vistas de React. Puede usar eventos de Backbone como lo hace con las vistas de Backbone. He incursionado en ambos y no vi mucha diferencia, excepto que uno es un mixin y el otro cambia React.createClassa React.createBackboneClass.

Blaine Hatab
fuente
Por favor, tenga cuidado con esos puentes "controlados por eventos" entre react y backbone El primer complemento usa setProps sin tener en cuenta el nivel de montaje del componente. Es un error. Second depende en gran medida de forceUpdate y piensa que solo se puede asignar un modelo para un componente, lo cual no es una situación común. Además, si comparte el modelo en una interfaz de usuario compleja con componentes de reacción y ha olvidado cancelar la suscripción a eventos de actualización inútiles que causan forceUpdate, entonces puede caer en la representación recursiva, tenga en cuenta eso.
wallice