Qual é a melhor maneira de acionar o evento onchange em react js

112

Usamos o pacote Backbone + ReactJS para construir um aplicativo do lado do cliente. Contando fortemente com o notório valueLink, propagamos valores diretamente para o modelo por meio do próprio invólucro que suporta a interface ReactJS para vinculação bidirecional.

Agora enfrentamos o problema:

Temos um jquery.mask.jsplugin que formata o valor de entrada programaticamente, portanto, não dispara eventos React. Tudo isso leva a uma situação em que o modelo recebe valores não formatados da entrada do usuário e perde os formatados do plugin.

Parece que o React tem muitas estratégias de manipulação de eventos, dependendo do navegador. Existe alguma maneira comum de acionar o evento de alteração para um elemento DOM específico para que o React o ouça?

Wallice
fonte
👉 Discussão do React Github: Trigger simulado mudança de valor de entrada para React 16
vsync

Respostas:

174

Para React 16 e React> = 15,6

Setter .value=não está funcionando como queríamos porque a biblioteca React substitui o setter de valor de entrada, mas podemos chamar a função diretamente no 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 elemento textarea você deve usar prototypede HTMLTextAreaElementclasse.

Novo exemplo de codepen .

Todos os créditos para este contribuidor e sua solução

Resposta desatualizada apenas para React <= 15,5

Com react-dom ^15.6.0você pode usar o simulatedsinalizador no objeto de evento para o evento passar

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

Eu fiz um codepen com um exemplo

Para entender por que um novo sinalizador é necessário, achei este comentário muito útil:

A lógica de entrada no React agora elimina a duplicação dos eventos de mudança para que eles não disparem mais de uma vez por valor. Ele ouve os eventos onChange / onInput do navegador e também os conjuntos na propriedade de valor do nó DOM (quando você atualiza o valor via javascript). Isto tem o efeito colateral de significar que se você atualizar o valor da entrada manualmente input.value = 'foo' então despachar um ChangeEvent com {target: input} React irá registrar tanto o conjunto quanto o evento, veja se seu valor ainda é `'foo ', considere-o um evento duplicado e engula-o.

Isso funciona bem em casos normais porque um evento "real" iniciado pelo navegador não aciona conjuntos no element.value. Você pode escapar dessa lógica secretamente ao marcar o evento que você disparou com um sinalizador simulado e reagir sempre disparará o evento. https://github.com/jquense/react/blob/9a93af4411a8e880bbc05392ccf2b195c97502d1/src/renderers/dom/client/eventPlugins/ChangeEventPlugin.js#L128

Sorriso
fonte
1
Obrigado, isso funcionou a partir do react-dom ^ 15.6.0. Mas parece que após o React 16.0 isso parou de funcionar. Alguma ideia sobre a alternativa de usar a bandeira simulada para acionar os eventos de mudança?
Qwerty
Acredite que há um ponto aqui que daria a dica: reactjs.org/blog/2017/09/26/react-v16.0.html#breaking-changes Tem alguma ideia do que poderia ser?
Qwerty
@Qwerty Eu atualizei minha resposta, pode ser que funcione para você
Sorrir
e o que dizer do botão onClick? o que deve ser feito para desencadear esse tipo de evento?
Bender
@Bender, basta chamar o método de clique nativo no elemento
Sorrir,
63

Pelo menos em entradas de texto, parece que onChangeestá ouvindo eventos de entrada:

var event = new Event('input', { bubbles: true });
element.dispatchEvent(event);
Michael
fonte
Depende da versão do navegador. O IE8 não oferece suporte a eventos de entrada. E ie9 não dispara evento de entrada quando você remove caracteres da caixa de texto. developer.mozilla.org/en-US/docs/Web/Events/input
wallice
Eventnão é compatível com o IE https://developer.mozilla.org/en-US/docs/Web/API/Event/Event
Joyful
1
O IE8 não é mais compatível com o React. Para o IE9, você pode usar algo como var event = document.createEvent('CustomEvent'); event.initCustomEvent('input', true, false, { });, mas não tenho uma VM do IE9 à mão.
Michael
@Michael Estou testando seu código no IE11 e não está funcionando nos campos de entrada reactjs. Ele funciona em entradas HTML normais. Também funciona no 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

Sei que essa resposta chega um pouco tarde, mas recentemente enfrentei um problema semelhante. Eu queria acionar um evento em um componente aninhado. Eu tinha uma lista com widgets do tipo rádio e caixa de seleção (eram divs que se comportavam como caixas de seleção e / ou botões de rádio) e em algum outro lugar do aplicativo, se alguém fechasse uma caixa de ferramentas, eu precisava desmarcar uma.

Eu encontrei uma solução muito simples, não tenho certeza se essa é a melhor prática, mas funciona.

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

Isso acionou o evento click no domNode e meu manipulador anexado via react foi de fato chamado para se comportar como eu esperaria se alguém clicasse no elemento. Não testei o onChange, mas deve funcionar, e não tenho certeza de como isso funcionará em versões realmente antigas do IE, mas acredito que o MouseEvent é suportado pelo menos no IE9 e superior.

Eventualmente, afastei-me disso para meu caso de uso específico porque meu componente era muito pequeno (apenas uma parte de meu aplicativo costumava reagir, pois ainda estou aprendendo) e eu poderia fazer a mesma coisa de outra maneira sem obter referências a nós dom.

ATUALIZAR:

Como outros afirmaram nos comentários, é melhor usar this.refs.refnamepara obter uma referência a um nó dom. Neste caso, refname é o ref que você anexou ao seu componente por meio <MyComponent ref='refname' />.

Robert-W
fonte
1
Em vez de ID, você pode usar a React.findDOMNodefunção. goo.gl/RqccrA
m93a
2
> Em vez de ID, você pode usar a função React.findDOMNode. Ou adicione um refao seu elemento e usethis.ref.refName.dispatchEvent
silkAdmin
A estrutura provavelmente mudou desde então, é this.ref s .refname
nclord
1
String refs são considerados legados agora e serão descontinuados no futuro. Refs de retorno de chamada são o método preferido para rastrear o nó DOM.
dzv3
9

Você pode simular eventos usando ReactTestUtils, mas isso é projetado para teste de unidade.

Eu recomendo não usar valueLink para este caso e simplesmente ouvir os eventos de mudança disparados pelo plug-in e atualizar o estado da entrada em resposta. A vinculação bidirecional é mais uma demonstração do que qualquer outra coisa; eles são incluídos em complementos apenas para enfatizar o fato de que a vinculação bidirecional pura não é apropriada para a maioria dos aplicativos e que você geralmente precisa de mais lógica de aplicativo para descrever as interações em seu aplicativo.

Sophie Alpert
fonte
Eu estava com medo de que a resposta fosse algo assim (. O problema é que o React é muito rígido com o fluxo de trabalho de envio / recebimento. Em particular, é necessário especificar o manipulador onChange para poder reagir à entrada do usuário; nenhuma outra maneira, exceto o estado não controlado, que é gentil de over-boilerplate para mim. No meu caso, devo declarar este manipulador apenas para seguir as regras, enquanto a entrada relevante virá do evento onchange acionado por jquery. Realmente, o React não tem ideia de estender endpoints de envio / recebimento com código declarado pelo usuário
wallice
1
E ... no caso de você querer usar ReactTestUtils ...ReactTestUtils.Simulate.change(ReactDOM.findDOMNode(this.fieldRef))
colllin
8

Expandindo a resposta de Grin / Dan Abramov, isso funciona em vários tipos de entrada. Testado em 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
fonte
5
Não funciona para elementos selecionados. Você precisa de 'mudança' em vez de 'entrada' para o evento.
styks
3

O disparo de eventos de mudança em elementos arbitrários cria dependências entre componentes que são difíceis de raciocinar. É melhor seguir o fluxo de dados unilateral do React.

Não existe um snippet simples para acionar o evento de mudança do React. A lógica é implementada em ChangeEventPlugin.js e existem diferentes ramificações de código para diferentes tipos de entrada e navegadores. Além disso, os detalhes de implementação variam entre as versões do React.

Eu construí o react-trigger-change que faz a coisa, mas se destina a ser usado para teste, não como uma dependência de produção:

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

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

CodePen

Vitaly Kuznetsov
fonte
1

bem, uma vez que usamos funções para lidar com um evento onchange, podemos fazer assim:

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)} />
  );
 }
}
quatro olhos-04-04
fonte
1

Eu encontrei isso nos problemas do Github do React: Funciona como um encanto (v15.6.2)

Aqui está como eu implementei em uma 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
fonte
Isso não funciona se o valor da caixa de texto for igual ao valor que você está passando para setNativeValue
Arun
0

Para HTMLSelectElement, ou seja<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
fonte
-1

Se você estiver usando o Backbone e o React, recomendo um dos seguintes,

Ambos ajudam a integrar modelos e coleções de Backbone com visualizações React. Você pode usar eventos do Backbone da mesma forma que faz com as visualizações do Backbone. Eu já experimentei ambos e não vi muita diferença, exceto um é um mixin e o outro muda React.createClasspara React.createBackboneClass.

Blaine Hatab
fonte
Por favor, tome cuidado com aquelas pontes "orientadas por evento" entre react e backbone. O primeiro plugin usa setProps desconsiderando o nível de montagem do componente. É um erro. O segundo depende fortemente de forceUpdate e considera que apenas um modelo pode ser atribuído a um componente, o que não é uma situação comum. Além disso, se você compartilha o modelo através da interface do usuário complexa com componentes de reação e se esqueceu de cancelar a inscrição de eventos de atualização inúteis que causam forceUpdate, você pode cair na renderização recursiva, esteja ciente disso.
wallice