React.js: CRUD Rest
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';
import { isDisabled } from '@testing-library/user-event/dist/utils';
/*
* 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;