Aprenda React - Eventos e Formulários

January 27, 2019

Olá pessoal. No último post sobre React, falamos sobre componentes, state e props. Neste, vamos falar um pouco sobre eventos e formulários (forms) no React. Estes são dois assuntos vitais para qualquer web app, e apesar de não ser nenhum bicho de sete cabeças no React, são assuntos que apresentam certas particularidades.

Vamos partir do mesmo boilerplate que temos usados nos posts anteriores sobre React, e você pode encontrá-lo aqui: https://github.com/felipegalvao/webpack-4-react-boilerplate

Vamos lá então!

Eventos

Em primeiro lugar, o que são eventos? Eventos são utilizados para notificar o código de que algo interessante está ocorrendo. Este algo interessante pode ser causado tanto pelo usuário quanto pela própria página. Por exemplo, um usuário clicar em um botão, ou uma página terminar de carregar, ou o valor de um input de texto ser alterado.

Esclarecido o que são eventos, podemos continuar. Eventos no React são tratados de forma semelhante à eventos em HTML comum e Javascript básico, mas com algumas diferenças. Vamos ver o exemplo do evento onClick. Enquanto que no HTML faríamos:

<button href="#" onclick="alert('row was added!');">
  Add row
</button>

No React, colocamos o código dentro das chaves, e ficaria assim (já com todo o código do componente:

import React, { Component } from 'react';
import ReactDOM from 'react-dom';

import './styles/main.scss';

class Index extends Component {
  render() {
    return <div>
      <button onClick={ () => alert('row was added!') }>add row</button>
    </div>;
  }
};

ReactDOM.render(<Index />, document.getElementById("index"));

No caso do React, precisamos utilizar arrow function para colocar o código dessa forma, para que o mesmo não seja executado quando o componente for renderizado. Também é possível chamar uma função definida previamente. No HTML ficaria assim:

<button onclick="handleClick()">
  add row
</button>

Já no React, novamente, vamos incluir a função dentro de chaves. Como já vimos no post sobre state e props, para que o this funcione corretamente no callback, é necessário fazer o bind do mesmo no constructor do componente:

import React, { Component } from 'react';
import ReactDOM from 'react-dom';

import './styles/main.scss';

class Index extends Component {
  constructor(props) {
    super(props);
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    alert('row was added!');
  }

  render() {
    return <div>
      <button onClick={ this.handleClick }>add row</button>
    </div>;
  }
};

ReactDOM.render(<Index />, document.getElementById("index"));

Caso isto lhe incomode, existem duas outras formas para fazer a função funcionar corretamente, que explico no mesmo post. Eu, particularmente, prefiro o uso de arrow functions para resolver esse tipo de problema.

Bem, feito o código acima, ao clicar no botão exibido, você receberá o alerta do navegador. O onClick é o tipo de evento mais comum para botões.

Outro evento bastante comum é o onChange, muito utilizado em conjunto com o elemento input. O onChange é disparado a cada vez que o valor do input é alterado. Vamos testá-lo utilizando o código abaixo:

import ReactDOM from 'react-dom';

import './styles/main.scss';

class Index extends Component {
  constructor(props) {
    super(props);
    this.handleClick = this.handleClick.bind(this);
    this.handleTextChange = this.handleTextChange.bind(this);
    this.state = {currentText: ''}
  }

  handleClick() {
    alert('row was added!');
  }

  handleTextChange(event) {
    this.setState({currentText: event.target.value});
  }

  render() {
    return <div>
      <button onClick={ this.handleClick }>add row</button>
      <input
        type="text"
        placeholder="enter your name here"
        onChange={ this.handleTextChange }
      />
      { this.state.currentText }
    </div>;
  }
};

ReactDOM.render(<Index />, document.getElementById("index"));

Digite algum texto no input e veja como o valor do parágrafo vai se alterando a medida que cada novo caractere é incluído. Repare que também precisamos definir um estado inicial para o texto com uma string vazia, para que o parágrafo não retorne um erro ao entrar na página pela primeira vez.

Formulários - componentes controlados

Após termos aprendido sobre eventos, podemos falar sobre formulários. Formulários são parte integrante de uma grande quantidade de web apps, e por isso, é importante entender como os mesmos funcionam no React.

Para formulários, o mais comum em React é utilizar o conceito de componentes controlados. Para isso, fazemos com que o estado seja a fonte dos dados do formulário, o chamado “single source of truth”, e vamos apenas atualizando o estado com as mudanças e utilizando seus valores para serem exibidos nos elementos do formulário.Para um formulário simples, apenas com um input, ficaria assim:

import React, { Component } from 'react';
import ReactDOM from 'react-dom';

import './styles/main.scss';

class Index extends Component {
  constructor(props) {
    super(props);
    this.handleSubmit = this.handleSubmit.bind(this);
    this.handleTextChange = this.handleTextChange.bind(this);
    this.state = {name: ''}
  }

  handleSubmit(event) {
    alert('Your name was sent to our API, ' + this.state.name);
    event.preventDefault();
  }

  handleTextChange(event) {
    this.setState({name: event.target.value});
  }

  render() {
    return <div>
      <form onSubmit={this.handleSubmit}>
        <label>
          Name:
          <input
            type="text"
            placeholder="enter your name here"
            onChange={ this.handleTextChange }
            value={ this.state.currentText }
          />
        </label>
        <input type="submit" value="Send" />
      </form>
    </div>;
  }
};

ReactDOM.render(<Index />, document.getElementById("index"));

Digite seu nome e clique no botão de Send, e verá o alerta com o nome que foi digitado no input. Vamos adicionar mais um elemento. Colocaremos um select:

import React, { Component } from 'react';
import ReactDOM from 'react-dom';

import './styles/main.scss';

class Index extends Component {
  constructor(props) {
    super(props);
    this.handleSubmit = this.handleSubmit.bind(this);
    this.handleTextChange = this.handleTextChange.bind(this);
    this.handleColorSelect = this.handleColorSelect.bind(this);
    this.state = {name: '', favoriteColor: 'blue'}
  }

  handleSubmit(event) {
    alert(
      `Your name is ${this.state.name} and your favorite color is ${this.state.favoriteColor}`
    );
    event.preventDefault();
  }

  handleTextChange(event) {
    this.setState({name: event.target.value});
  }

  handleColorSelect(event) {
    this.setState({favoriteColor: event.target.value});
  }

  render() {
    return <div>
      <form onSubmit={this.handleSubmit}>
        <label>
          Name:
          <input
            type="text"
            placeholder="enter your name here"
            onChange={ this.handleTextChange }
            value={ this.state.currentText }
          />
        </label>
        <select value={this.state.favoriteColor} onChange={this.handleColorSelect}>
          <option value="blue">Blue</option>
          <option value="red">Red</option>
          <option value="green">Green</option>
          <option value="black">Black</option>
        </select>
        <input type="submit" value="Send" />
      </form>
    </div>;
  }
};

ReactDOM.render(<Index />, document.getElementById("index"));

No caso do select, você cria cada opção dentro do elemento com seu próprio valor, e passa o evento para a função a ser executada quando o valor do select for alterado, e pronto. Faça o teste, escreva seu nome e escolha sua cor preferida, e então clique no botão para enviar o formulário.

Por fim, vamos mostrar os radio buttons. O funcionamento deles é bem similar ao select. Vou colocar alguns div para organizar melhor o report, e então vou adicionar os radio buttons:

import React, { Component } from 'react';
import ReactDOM from 'react-dom';

import './styles/main.scss';

class Index extends Component {
  constructor(props) {
    super(props);
    this.handleSubmit = this.handleSubmit.bind(this);
    this.handleTextChange = this.handleTextChange.bind(this);
    this.handleColorSelect = this.handleColorSelect.bind(this);
    this.handleAnimalSelect = this.handleAnimalSelect.bind(this);
    this.state = {name: '', favoriteColor: 'blue', favoriteAnimal: ''}
  }

  handleSubmit(event) {
    alert(
      `Your name is ${this.state.name}, your favorite color is ${this.state.favoriteColor}` +
      `and your favorite animal is ${this.state.favoriteAnimal}`
    );
    event.preventDefault();
  }

  handleTextChange(event) {
    this.setState({name: event.target.value});
  }

  handleColorSelect(event) {
    this.setState({favoriteColor: event.target.value});
  }

  handleAnimalSelect(event) {
    this.setState({favoriteAnimal: event.target.value});
  }

  render() {
    return <div>
      Insert your name, your favorite color and your favorite animal.
      <form onSubmit={this.handleSubmit}>
        <div>
          <label>
            Name:
            <input
              type="text"
              placeholder="enter your name here"
              onChange={ this.handleTextChange }
              value={ this.state.currentText }
            />
          </label>
        </div>
        <div>
          <select value={this.state.favoriteColor} onChange={this.handleColorSelect}>
            <option value="blue">Blue</option>
            <option value="red">Red</option>
            <option value="green">Green</option>
            <option value="black">Black</option>
          </select>
        </div>
        <div>
          <label>
            <input
              type="radio"
              name="react-tips"
              value="dog"
              checked={this.state.favoriteAnimal === "dog"}
              onChange={this.handleAnimalSelect}
            />
            Dog
          </label>
        </div>
        <div>
          <label>
            <input
              type="radio"
              name="react-tips"
              value="cat"
              checked={this.state.favoriteAnimal === "cat"}
              onChange={this.handleAnimalSelect}
            />
            Cat
          </label>
        </div>
        <div>
          <input type="submit" value="Send" />
        </div>
      </form>
    </div>
  }
};

ReactDOM.render(<Index />, document.getElementById("index"));

Note que para os radio buttons, definimos se o mesmo está selecionado ou não comparando o seu valor com o valor que está definido no state no momento.

E assim, conseguimos ver a forma mais utilizada para trabalhar com formulários no React, além de algumas aplicações dos elementos mais utilizados com formulários.

Em exemplos da “vida real”, submeter o formulário poderia fazer um request para uma API, possivelmente usando Axios, fetch ou qualquer outra forma de sua preferência. Mas isso é assunto para outro post.

Abraços e espero que tenha ajudado.