React.js: identificando diferentes entradas com um manipulador onChange

141

Curioso, qual é a maneira correta de abordar isso:

var Hello = React.createClass({
getInitialState: function() {
    return {total: 0, input1:0, input2:0};
},
render: function() {
    return (
        <div>{this.state.total}<br/>
            <input type="text" value={this.state.input1} onChange={this.handleChange} />
            <input type="text" value={this.state.input2} onChange={this.handleChange} />
        </div>
    );
},
handleChange: function(e){
    this.setState({ ??? : e.target.value});
    t = this.state.input1 + this.state.input2;
    this.setState({total: t});
}
});

React.renderComponent(<Hello />, document.getElementById('content'));

Obviamente, você pode criar funções handleChange separadas para lidar com cada entrada diferente, mas isso não é muito bom. Da mesma forma, você pode criar um componente apenas para uma entrada individual, mas eu queria ver se há uma maneira de fazer isso assim.

T3db0t
fonte

Respostas:

162

Sugiro aderir a atributos HTML padrão, como nameno inputElements, para identificar suas entradas. Além disso, você não precisa manter "total" como um valor separado no estado, pois é possível compor adicionando outros valores ao seu estado:

var Hello = React.createClass({
    getInitialState: function() {
        return {input1: 0, input2: 0};
    },
    render: function() {
        const total = this.state.input1 + this.state.input2;

        return (
            <div>{total}<br/>
                <input type="text" value={this.state.input1} name="input1" onChange={this.handleChange} />
                <input type="text" value={this.state.input2} name="input2" onChange={this.handleChange} />
            </div>
        );
    },
    handleChange: function(e) {
        this.setState({[e.target.name]: e.target.value});
    }
});

React.renderComponent(<Hello />, document.getElementById('content'));
Ross Allen
fonte
3
Eu estava tentando fazer isso inicialmente, mas setState ({e.target.name ... não funciona - é um erro de sintaxe. Você precisa criar um objeto temporário com obj [e.target.name] e setState como esse. Isso funciona, mas parece haver algum tipo de atraso na atualização do objeto de estado.
T3db0t
1
O "tipo de atraso" está atualizando setState durante requestAnimationFrame (presumo), então ignorar essa observação
T3db0t
24
@ T3db0t Esta sintaxe do ES6 funciona:this.setState({ [e.target.name]: e.target.value });
XåpplI'-I0llwlg'I -
2
Usar o bind é a melhor abordagem. Essa abordagem não funcionará se você anexar o manipulador onChange a um elemento sem uma propriedade name, como uma div.
ericgrosse
3
Neste caso, o @ericgrosse bind não é melhor, pois você cria muitas funções desnecessárias. Além disso, você poderia usar um atributo data- ou algo em um tipo diferente de elemento se necessário
aw04
106

Você pode usar o .bindmétodo para criar previamente os parâmetros para o handleChangemétodo. Seria algo como:

  var Hello = React.createClass({
    getInitialState: function() {
        return {input1:0, 
                input2:0};
    },
    render: function() {
      var total = this.state.input1 + this.state.input2;
      return (
        <div>{total}<br/>
          <input type="text" value={this.state.input1} 
                             onChange={this.handleChange.bind(this, 'input1')} />
          <input type="text" value={this.state.input2} 
                             onChange={this.handleChange.bind(this, 'input2')} />
        </div>
      );
    },
    handleChange: function (name, e) {
      var change = {};
      change[name] = e.target.value;
      this.setState(change);
    }
  });

  React.renderComponent(<Hello />, document.getElementById('content'));

(Também fiz com que totalfosse computado no momento da renderização, pois é a coisa recomendada a fazer.)

fiatjaf
fonte
13
Apenas um lembrete rápido que this.handleChange.bind(...)cria uma nova função. No caso de você estiver usando shouldComponentUpdatecom PureRenderMixinou algo semelhante ele vai quebrar. Todos os seus componentes de entrada serão renderizados novamente quando um único valor for alterado.
Zemirco 01/06
5
Se você alterar a implementação de handleChangepara, handleChange(name) { return event => this.setState({name: event.target.value}); }poderá passar a função "mesma" (não criará uma nova função como é feita usando .bind()Then, você pode passar essa função:this.handleChange("input1")
Andreyco
1
Também acho importante mencionar que a natureza de setState é que não será possível recuperar o estado atual na função handChange, mas o estado anterior usando this.state.input1, por exemplo. Uma abordagem adequada será criar um retorno de chamada, como this.setState(change, function () { console.log(this.state.input1); );referência: stackoverflow.com/questions/30782948/…
Charlie-Greenman 04/04
39

O onChangeevento borbulha ... Então você pode fazer algo assim:

// A sample form
render () {
  <form onChange={setField}>
    <input name="input1" />
    <input name="input2" />
  </form>
}

E seu método setField pode ficar assim (supondo que você esteja usando o ES2015 ou posterior:

setField (e) {
  this.setState({[e.target.name]: e.target.value})
}

Eu uso algo semelhante a isso em vários aplicativos, e é bastante útil.

Christopher Davies
fonte
11

Solução preterida

valueLink/checkedLinkforam descontinuados do núcleo React, porque está confundindo alguns usuários. Esta resposta não funcionará se você usar uma versão recente do React. Mas se você gosta, pode emular facilmente criando seu próprio Inputcomponente

Conteúdo antigo da resposta:

O que você deseja alcançar pode ser muito mais facilmente alcançado usando os auxiliares de ligação de dados bidirecionais do React.

var Hello = React.createClass({
    mixins: [React.addons.LinkedStateMixin],
    getInitialState: function() {
        return {input1: 0, input2: 0};
    },

    render: function() {
        var total = this.state.input1 + this.state.input2;
        return (
            <div>{total}<br/>
                <input type="text" valueLink={this.linkState('input1')} />;
                <input type="text" valueLink={this.linkState('input2')} />;
            </div>
        );
    }

});

React.renderComponent(<Hello />, document.getElementById('content'));

Fácil né?

http://facebook.github.io/react/docs/two-way-binding-helpers.html

Você pode até implementar seu próprio mixin

Sebastien Lorber
fonte
Por que temos que ligar um onchange para definir o estado. É ReactJS afinal!
Junaid Qadir
Observe que esta solução está obsoleta.
Philipp
6

Você também pode fazer assim:

...
constructor() {
    super();
    this.state = { input1: 0, input2: 0 };
    this.handleChange = this.handleChange.bind(this);
}

handleChange(input, value) {
    this.setState({
        [input]: value
    })
}

render() {
    const total = this.state.input1 + this.state.input2;
    return (
        <div>
            {total}<br />
            <input type="text" onChange={e => this.handleChange('input1', e.target.value)} />
            <input type="text" onChange={e => this.handleChange('input2', e.target.value)} />
        </div>
    )
}
inostia
fonte
não funcionou para mim. vi Ferramentas de desenvolvedor do React. a sua criação e a atribuição de valores de variável / objeto chamada de entrada, em vez de input1 / input2
Gaurravs
1
@Gaurravs sim, você está certo. Eu precisava para adicionar []ao redor inputem setState. Isso faz com que a propriedade dynamicaly atribuído
inostia
4

Você pode usar um Reactatributo especial chamado refe, em seguida, combinar os nós DOM reais no onChangeevento usando Reacta getDOMNode()função de:

handleClick: function(event) {
  if (event.target === this.refs.prev.getDOMNode()) {
    ...
  }
}

render: function() {
  ...
  <button ref="prev" onClick={this.handleClick}>Previous question</button>
  <button ref="next" onClick={this.handleClick}>Next question</button>
  ...
}
Janusz Kacalak
fonte
Pelo menos em 0,13, this.refs não possui uma propriedade pref.
blub
2
@usagidon - prevé apenas um nome, que pus no render()método
Janusz Kacalak
2
A partir do React v0.14, você pode fazer isso event.target === this.refs.prev. facebook.github.io/react/blog/2015/10/07/…
XåpplI'-I0llwlg'I -
0

@Vigril Disgr4ce

Quando se trata de formulários de vários campos, faz sentido usar o recurso principal do React: componentes.

Nos meus projetos, crio componentes TextField, que assumem um valor mínimo e cuidam de lidar com comportamentos comuns de um campo de texto de entrada. Dessa forma, você não precisa se preocupar em acompanhar os nomes dos campos ao atualizar o estado do valor.

[...]

handleChange: function(event) {
  this.setState({value: event.target.value});
},
render: function() {
  var value = this.state.value;
  return <input type="text" value={value} onChange={this.handleChange} />;
}

[...]
manu3569
fonte
0

Você pode acompanhar o valor de cada filho input, criando um InputFieldcomponente separado que gerencia o valor de um único input. Por exemplo, InputFieldpoderia ser:

var InputField = React.createClass({
  getInitialState: function () {
    return({text: this.props.text})
  },
  onChangeHandler: function (event) {
     this.setState({text: event.target.value})
  }, 
  render: function () {
    return (<input onChange={this.onChangeHandler} value={this.state.text} />)
  }
})

Agora, o valor de cada um inputpode ser rastreado em uma instância separada desse InputFieldcomponente sem criar valores separados no estado do pai para monitorar cada componente filho.

Javasamurai
fonte
0

Vou fornecer uma solução realmente simples para o problema. Suponha que tenhamos duas entradas usernameepassword , mas queremos que nosso identificador seja fácil e genérico, para podermos reutilizá-lo e não escrever código padrão.

I.Our formulário:

                <form>
                    <input type="text" name = "username" onChange={this.onChange} value={this.state.username}/>
                    <input type="text" name = "password" onChange={this.onChange} value={this.state.password}/>
                    <br></br>
                    <button type="submit">Submit</button>
                </form>

II.O nosso construtor, que queremos salvar usernamee password, para que possamos acessá-los facilmente:

constructor(props) {
    super(props);
    this.state = {
        username: '',
        password: ''
    };

    this.onSubmit = this.onSubmit.bind(this);
    this.onChange = this.onChange.bind(this);
}

III.O identificador interessante e "genérico", com apenas um onChangeevento, é baseado nisso:

onChange(event) {
    let inputName = event.target.name;
    let value = event.target.value;

    this.setState({[inputName]:value});


    event.preventDefault();
}

Deixe-me explicar:

1. Quando uma alteração é detectada, onChange(event)é chamada

2.Em seguida, obtemos o parâmetro name do campo e seu valor:

let inputName = event.target.name; ex: username

let value = event.target.value; ex: itsgosho

3.Com base no parâmetro name, obtemos nosso valor do estado no construtor e atualizamos com o valor:

this.state['username'] = 'itsgosho'

4.A chave a ser observada aqui é que o nome do campo deve corresponder ao nosso parâmetro no estado

Espero ter ajudado alguém de alguma forma :)

Georgi Peev
fonte
0

A chave do seu estado deve ser igual ao nome do seu campo de entrada. Então você pode fazer isso no método handleEvent;

this.setState({
        [event.target.name]: event.target.value
});
Rajan
fonte
-1

Oi melhoramos resposta ssorallen . Você não precisa vincular a função porque pode acessar a entrada sem ela.

var Hello = React.createClass({
    render: function() {
        var total = this.state.input1 + this.state.input2;
        return (
             <div>{total}<br/>
                  <input type="text" 
                    value={this.state.input1}
                    id="input1"  
                    onChange={this.handleChange} />
                 <input type="text" 
                    value={this.state.input2}
                    id="input2" 
                    onChange={this.handleChange} />
            </div>
       );
   },
   handleChange: function (name, value) {
       var change = {};
       change[name] = value;
       this.setState(change);
   }
});

React.renderComponent(<Hello />, document.getElementById('content'));
Albert Olivé
fonte