React.js: CRUD Rest

De Aulas



Afluentes: Usabilidade, Desenvolvimento Web, Mobile e Jogos, Desenvolvimento Front-end II

Serviço web

Para que nosso CRUD possa ser funcional, precisamos de um serviço web com as operações de CRUD (CREATE, READ, UPDATE e DELETE). Para o nosso exemplo aqui, usarei um serviço web desenvolvido em linguagem de programação GO:

Caso você queira reimplementar o serviço em Node.js ou outra linguagem/framework, basta entender o manual de acesso ao serviço web:

Criação do Projeto

Tendo definido o serviço web que iremos consumir, agora vamos para a criação do nosso projeto. Nesse ponto, já temos instalado o npm, node e create-react-app, então vamos criar a pasta do projeto:

$ npx create-react-app reactcrud

Também vamos precisar do Axios, então já vamos instalar ele. Para isso, entre dentro da pasta que do projeto que criamos e baixe o axios:

$ cd reactcrud
$ npm install --save axios

Depois disso, vamos alterar alguns arquivos.

index.css

Para que a tabela fique bonitinha, vamos adicionar um CSS no arquivo index.css

table {
  border-collapse: collapse;
  width: 100%;
}

th,
td {
  text-align: left;
  padding: 8px;
}

tr:nth-child(even) {
  background-color: #ddddff
}

tr:hover {
  background-color: #bbbbff;
}

th {
  background-color: #04AA6D;
  color: white;
}

App.js

E então vamos para o arquivo principal da aplicação

import React, { Component } from 'react';
import axios from 'axios';
import './App.css';

/*
 * Abaixo temos algumas configurações globais do axios. É uma
 * outra forma de iniciar algumas coisas que serão usadas por
 * todos os endpoints, tais como: link do serviço principal,
 * o usuário e a chave de acesso ao serviço.
 * Nesse exemplo estamos acessando o localhost, mas tenho uma
 * api para testes no link: https://api.arisa.com.br
 */
axios.defaults.baseURL = 'http://localhost:8080';
axios.defaults.headers.common['X-User'] = 'fulano';
axios.defaults.headers.common['X-API-KEY'] = '12345';

class App extends Component {
  /*
   * No constructor inicializamos nossa herança (Component) e
   * instanciamos nossos estados, sendo a lista de usuários, o
   * userData com as informações id, nome e email referente aos
   * campos de entrada. E o disabled, que é para desabilitarmos
   * o campo ID quano formos alterar algum registro
   */
  constructor(props) {
    super(props);
    this.state = {
      users: [],
      userData: { id: '', name: '', email: '' },
      isDisabled: false,
    };
  }

  /*
   * Esse método é chamado depois que nossa aplicação tiver sido criada.
   * Ele apenas faz chama o método fetchUsers para carregar os dados da
   * base de dados via serviço web.
   */
  componentDidMount() {
    this.fetchUsers();
  }

  /**
   * Aqui usamos nosso GET sem parâmetro para retornar toda a lista
   * de registros, e ao executar o setState, a tabela é atualizada
   * com as novas informações.
   */
  fetchUsers = async () => {
    try {
      const response = await axios.get('/users');
      this.setState({ users: response.data });
    } catch (error) {
      console.error("Erro ao buscar usuários:", error);
    }
  };

  /*
   * Esse evento é chamado sempre que alterarmos alguma informação
   * nos campos de entrada, de forma a atualizar também os estados
   */
  handleChange = (event) => {
    const { name, value } = event.target;
    /**
     * Se o campo for "id" gente converte para número.
     * O ...prevState.userData atualiza os campos com o estado
     * anterior.
     * Depois só modificamos no state a informação que foi
     * alterada no campo de entrada. 
     */
    this.setState((prevState) => ({
      userData: {
        ...prevState.userData,
        [name]: name === 'id' ? Number(value) : value,
      }
    }));
  };

  /*
   * Quando o botão gravar é clicado, o handleSubmit é chamado para
   * tratar o evento.
   */
  handleSubmit = async (event) => {
    event.preventDefault();
    const { userData, isDisabled } = this.state;
    try {
      if (isDisabled) {
        await axios.put('/users', userData);
      } else {
        await axios.post('/users', userData);
      }
      this.resetForm();
      this.fetchUsers();
    } catch (error) {
      console.error("Erro ao salvar usuário:", error);
    }
  };

  /**
   * O load user pega as informações do elemento da lista referente
   * à linha da tabela que clicamos e carrega as informações nos campos
   * texto. Isso ocorre porque usamos o setState que faz essa sincronização. 
   */
  loadUser = (user) => {
    this.setState({
      userData: { id: user.id, name: user.name, email: user.email },
      isDisabled: true,
    });
  };

  /**
   * Exclui um registro pelo "id" e recarrega os elementos da tabela.
   */
  deleteUser = async (id) => {
    try {
      await axios.delete(`/users/${id}`);
      this.fetchUsers();
      this.resetForm();
    } catch (error) {
      console.error("Erro ao deletar usuário:", error);
    }
  };

  /**
   * Reseta as informações do formulário ao limpar o state userData
   */
  resetForm = () => {
    this.setState({
      userData: { id: '', name: '', email: '' },
      isDisabled: false,
    });
  };

  /**
   * O render é onde criamos/renderizamos nossa página/aplicação.
   */
  render() {
    // Linkamos nossos estados localmente na função
    const { users, userData, isDisabled } = this.state;

    return (
      <div>
        <h1>Usuários</h1>
        <form onSubmit={this.handleSubmit}>
          <p>
            <label>
              ID:
              <input
                type="text"
                name="id"
                value={userData.id}
                onChange={this.handleChange}
                disabled={isDisabled}
              />
            </label>
          </p>
          <p>
            <label>
              Nome:
              <input
                type="text"
                name="name"
                value={userData.name}
                onChange={this.handleChange}
              />
            </label>
          </p>
          <p>
            <label>
              E-mail:
              <input
                type="email"
                name="email"
                value={userData.email}
                onChange={this.handleChange}
              />
            </label>
          </p>
          <p>
            <button type="submit">Gravar</button>
            <button type="button" onClick={this.resetForm}>Limpar</button>
          </p>
        </form>
        <table>
          <thead>
            <tr><th>ID</th><th>Nome</th><th>E-mail</th><th>Ações</th></tr>
          </thead>
          <tbody>
            {/* Executamos nosso iteractor para ler cada linha da tabela */}
            {users.map((user) => (
              <tr key={user.id}>
                <td>{user.id}</td>
                <td onClick={() => this.loadUser(user)}>{user.name}</td>
                <td><a href={`mailto:${user.email}`}>{user.email}</a></td>
                <td>
                  <button type="button" onClick={() => this.deleteUser(user.id)}>Excluir</button>
                </td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    );
  }
}

export default App;