Mudanças entre as edições de "React.js: CRUD Rest"

De Aulas
Linha 1: Linha 1:
 +
  
 
Afluentes: [[Usabilidade, Desenvolvimento Web, Mobile e Jogos]], [[Desenvolvimento Front-end II]]
 
Afluentes: [[Usabilidade, Desenvolvimento Web, Mobile e Jogos]], [[Desenvolvimento Front-end II]]
Linha 58: Linha 59:
 
E então vamos para o arquivo principal da aplicação
 
E então vamos para o arquivo principal da aplicação
  
<syntaxhighlight lang=javascript>
+
<syntaxhighlight lang="javascript">
 
import React, { Component } from 'react';
 
import React, { Component } from 'react';
 
import axios from 'axios';
 
import axios from 'axios';
 
import './App.css';
 
import './App.css';
  
/**
+
/*
  * Abaixo temos o link do nosso servidor. Como exemplo, coloquei o link
+
  * Abaixo temos algumas configurações globais do axios. É uma
  * de onde coloco minhas APIs para teste no meu servidor online.  
+
* outra forma de iniciar algumas coisas que serão usadas por
  *  
+
* todos os endpoints, tais como: link do serviço principal,
  * const server = 'https://api.arisa.com.br';
+
  * o usuário e a chave de acesso ao serviço.
*
+
  * Nesse exemplo estamos acessando o localhost, mas tenho uma
* Mas para o desenvolvimenoto estou usando o localhost mesmo.
+
  * api para testes no link: https://api.arisa.com.br
 
  */
 
  */
const server = 'http://localhost:8080';
+
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 {
 
class App extends Component {
   /**
+
   /*
 
   * No constructor inicializamos nossa herança (Component) e
 
   * No constructor inicializamos nossa herança (Component) e
   * instanciamos nossos estados, sendo eles: lista de usuários,
+
   * instanciamos nossos estados, sendo a lista de usuários, o
   * id, nome e email referente aos campos de entrada. E o disabled
+
   * userData com as informações id, nome e email referente aos
   * é para desabilitarmos o campo ID quano formos alterar algum
+
  * campos de entrada. E o disabled, que é para desabilitarmos
  * registro
+
   * o campo ID quano formos alterar algum registro
  * @param {*} props
 
 
   */
 
   */
 
   constructor(props) {
 
   constructor(props) {
Linha 86: Linha 88:
 
     this.state = {
 
     this.state = {
 
       users: [],
 
       users: [],
       user_id: '',
+
       userData: { id: '', name: '', email: '' },
      user_name: '',
+
       isEditMode: false,
      user_email: '',
 
       disabled: false,
 
 
     };
 
     };
 
   }
 
   }
  
   /** Render é onde criamos a nossa página/aplicação. */
+
   /*
 +
  * 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 carregar 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, isEditMode } = this.state;
 +
    try {
 +
      if (isEditMode) {
 +
        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 },
 +
      isEditMode: 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: '' },
 +
      isEditMode: false,
 +
    });
 +
  };
 +
 
 +
  /**
 +
  * O render é onde criamos/renderizamos nossa página/aplicação.
 +
  */
 
   render() {
 
   render() {
 
     // Linkamos nossos estados localmente na função
 
     // Linkamos nossos estados localmente na função
     const { users } = this.state;
+
     const { users, userData, isEditMode } = this.state;
    const { user_id } = this.state;
 
    const { user_name } = this.state;
 
    const { user_email } = this.state;
 
  
 
     return (
 
     return (
Linha 106: Linha 204:
 
         <form onSubmit={this.handleSubmit}>
 
         <form onSubmit={this.handleSubmit}>
 
           <p>
 
           <p>
             <label>ID:
+
             <label>
               <input type='text'
+
              ID:
                 name='user_id'
+
               <input
                 value={user_id}
+
                type="text"
                 onChange={this.myChangeHandler}
+
                 name="id"
                 disabled={(this.state.disabled) ? "disabled" : ""}
+
                 value={userData.id}
 +
                 onChange={this.handleChange}
 +
                 disabled={isEditMode}
 
               />
 
               />
 
             </label>
 
             </label>
 
           </p>
 
           </p>
 
           <p>
 
           <p>
             <label>Name:
+
             <label>
               <input type='text'
+
              Nome:
                 name='user_name'
+
               <input
                 value={user_name}
+
                type="text"
                 onChange={this.myChangeHandler}
+
                 name="name"
 +
                 value={userData.name}
 +
                 onChange={this.handleChange}
 
               />
 
               />
 
             </label>
 
             </label>
 
           </p>
 
           </p>
 
           <p>
 
           <p>
             <label>E-mail:
+
             <label>
               <input type='text'
+
              E-mail:
                 name='user_email'
+
               <input
                 value={user_email}
+
                type="email"
                 onChange={this.myChangeHandler}
+
                 name="email"
 +
                 value={userData.email}
 +
                 onChange={this.handleChange}
 
               />
 
               />
 
             </label>
 
             </label>
 
           </p>
 
           </p>
 
           <p>
 
           <p>
             <button>Gravar</button>
+
             <button type="submit">Gravar</button>
             <button type='button' onClick={this.reset}>Limpar</button>
+
             <button type="button" onClick={this.resetForm}>Limpar</button>
 
           </p>
 
           </p>
 
         </form>
 
         </form>
 
         <table>
 
         <table>
 
           <thead>
 
           <thead>
             <tr><th>ID</th><th>Nome</th><th>E-mail</th><th></th></tr>
+
             <tr><th>ID</th><th>Nome</th><th>E-mail</th><th>Ações</th></tr>
 
           </thead>
 
           </thead>
 
           <tbody>
 
           <tbody>
             {users.map(user => (
+
            {/* Executamos nosso iteractor para ler cada linha da tabela */}
 +
             {users.map((user) => (
 
               <tr key={user.id}>
 
               <tr key={user.id}>
 
                 <td>{user.id}</td>
 
                 <td>{user.id}</td>
                 <td onClick={() => this.getUser(user.id)}>{user.name}</td>
+
                 <td onClick={() => this.loadUser(user)}>{user.name}</td>
                 <td><a href={"mailto:" + user.email}>{user.email}</a></td>
+
                 <td><a href={`mailto:${user.email}`}>{user.email}</a></td>
                 <td><button type='button' onClick={() => this.deleteUser(user.id)}>X</button></td>
+
                 <td>
 +
                  <button type="button" onClick={() => this.deleteUser(user.id)}>Excluir</button>
 +
                </td>
 
               </tr>
 
               </tr>
 
             ))}
 
             ))}
Linha 155: Linha 262:
 
       </div>
 
       </div>
 
     );
 
     );
  }
 
 
  /**
 
  * Esse método é assincrono e é chamado depois que nossa aplicação tiver
 
  * sido criada. Ele apenas faz chama o método get para carregar os dados
 
  * da base de dados via serviço web.
 
  */
 
  async componentDidMount() {
 
    this.get();
 
  }
 
 
  /**
 
  * Esse evento é chamado sempre que alterarmos alguma informação nos campos
 
  * de entrada, de forma a atualizar também os estados
 
  */
 
  myChangeHandler = (event) => {
 
    let nam = event.target.name;
 
    let val = event.target.value;
 
    this.setState({ [nam]: val });
 
  }
 
 
  /**
 
  * Quando o botão gravar é clicado, o handleSubmit é chamado para tratar o evento.
 
  * @param {*} e
 
  * @returns
 
  */
 
  handleSubmit = async (e) => {
 
    // Testamos se o Id pra gravar é válido
 
    try {
 
      var id = parseInt(this.state.user_id);
 
    } catch (error) {
 
      console.error("Error :", error);
 
    }
 
    // vamos garantir que seremos nós que vamos tratar esse evento
 
    e.preventDefault();
 
    /* Pegamos nossos dados dos campos de entrada e transformamos em
 
      uma string JSON */
 
    const data = JSON.stringify({
 
      id: id,
 
      name: this.state.user_name,
 
      email: this.state.user_email,
 
    });
 
    try {
 
      // Se o campo ID está desabilitado, então é um UPDATE. Usaremos o PUT
 
      // Caso contrário, será usado o POST. O JSON que criamos, "data" é
 
      // colocado no corpo da mensagem
 
      if (this.state.disabled) {
 
        await axios.put(`${server}/users`, data);
 
      } else {
 
        if (this.state.user_id.trim() === "") return;
 
        await axios.post(`${server}/users`, data);
 
      }
 
      // Limpamos os estados
 
      this.setState({
 
        user_id: '',
 
        user_name: '',
 
        user_email: '',
 
        disabled: false,
 
      });
 
      // Atualizamos a lista da tabela
 
      this.get();
 
    } catch (error) {
 
      console.error("Error creating post:", error);
 
    }
 
  };
 
 
  /**
 
  * Aqui usamos nosso GET sem parâmetro para retornar toda a lista
 
  * de registros
 
  */
 
  get = async () => {
 
    try {
 
      const response = await axios.get(`${server}/users`, '');
 
      this.setState({ users: [] });
 
      this.setState({ users: response.data });
 
    } catch (error) {
 
      console.error("Error get users :", error);
 
    }
 
  }
 
 
  /**
 
  * Esse método retorna apenas um objeto (registro). Para isso, precisamos
 
  * passar como parâmetro o Id do objeto que queremos retornar.
 
  * Veja que aqui carregamos nos campos texto as informações do json que é
 
  * retornado e desabilitamos o campo Id para não podermos alterar ele.
 
  * @param {} id
 
  */
 
  getUser = async (id) => {
 
    try {
 
      const response = await axios.get(`${server}/users/${id}`);
 
      this.setState({
 
        user_id: response.data.id,
 
        user_name: response.data.name,
 
        user_email: response.data.email,
 
        disabled: true,
 
      });
 
    } catch (error) {
 
      console.error("Error get user :", error);
 
    }
 
  }
 
 
  /**
 
  * Exclui um registro pelo Id. Depois limpa os estados e dá um
 
  * refresh na tabela
 
  * @param {*} id
 
  */
 
  deleteUser = async (id) => {
 
    try {
 
      await axios.delete(`${server}/users/${id}`);
 
      this.reset();
 
      this.get();
 
    } catch (error) {
 
      console.error("Error deleting post:", error);
 
    }
 
  }
 
 
  /** Apoenas limpa os estados e reabilita o campo Id */
 
  reset = async () => {
 
    this.setState({
 
      user_id: '',
 
      user_name: '',
 
      user_email: '',
 
      disabled: false,
 
    });
 
 
   }
 
   }
 
}
 
}

Edição das 12h24min de 29 de outubro de 2024


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: '' },
      isEditMode: 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 carregar 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, isEditMode } = this.state;
    try {
      if (isEditMode) {
        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 },
      isEditMode: 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: '' },
      isEditMode: false,
    });
  };

  /**
   * O render é onde criamos/renderizamos nossa página/aplicação.
   */
  render() {
    // Linkamos nossos estados localmente na função
    const { users, userData, isEditMode } = 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={isEditMode}
              />
            </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;