Requisições Http com ReactJS

Requisições HTTP com ReactJS

Quando desenvolvemos aplicações para web, é comum precisarmos acessar recursos que estão hospedados em um servidor, principalmente quando estamos trabalhando com uma estrutura modular, onde o front-end é separado do back-end e depende dele para consumir ou enviar informações.

Solicitar, enviar ou executar operações em outros servidores é um recurso realizado exclusivamente por solicitações HTTP.

Atualmente existem 5 métodos bem comuns que são utilizados durante nossas requisições HTTP, são eles:

  • GET
  • PUT
  • POST
  • PATCH
  • DELETE

Tais métodos nos permitem realizar operações de CRUD de forma padrão.

Na lição de hoje você vai aprender a fazer requisições HTTP usando alguns recursos existentes no Javascript (em conjunto com o ReactJS), tais como:

  • XMLHttpRequest
  • Fetch API
  • Axios

E no final desta lição, aprenderemos a criar um serviço responsável por realizar operações HTTP usando o padrão de design Facade.

Criando seu projeto de testes

Antes de começarmos, vamos criar um novo projeto em ReactJS dedicado a esta lição. No meu caso criei um novo projeto chamado de requisicoes-http:  

npx create-react-app requisicoes-http

Após isso, não se esqueça de fazer aquela limpeza do código, e também aquela organizada nas pastas do src (organização por funcionalidade) 😁

Relembrando a lição de requisições HTTP da jornada Javascript

A verdade, é que não tem como aprender ReactJS se você não sabe escrever códigos em Javascript (ou Typescript).

Aqui no portal da Micilini, nós temos uma lição bem completa que fala sobre o uso de solicitações HTTP usando o Javascript.

Lá foi ensinado a realização dessas requisições usando duas bibliotecas bastante utilizadas por desenvolvedores:

  • Fetch API
  • XMLHttpRequest

Recomendo que você dê uma olhada nessa lição antes de seguir para o próximo tópico, pois o objetivo aqui não é explicar detalhadamente o que cada biblioteca faz ou como ela funciona de fato, mas seu uso exclusivo em aplicações feitas com ReactJS.

Dito isto, vamos aprender a usar o XMLHttpRequest em um componente 😄

Usando o XMLHttpRequest no ReactJS (GET)

Para começar, vamos criar um novo componente chamado de XMLHTTPRequest dentro da pasta components.

XMLHTTPRequest > index.jsx:

import React, {useEffect} from 'react';

const XMLHTTPRequest = () => {
 return(
 <>
 <p>XMLHTTPRequest!</p>
 </>
 )
}

export default XMLHTTPRequest;

Não se esqueça de importá-lo também dentro do App.js:

import XMLHTTPRequest from "./components/XMLHTTPRequest";

export default function App() {
 return (
 <div>
 <XMLHTTPRequest />
 </div>
 );
}

No Javascript, nos temos o objeto XMLHttpRequest que nada mais é do que uma API usada para enviar solicitações HTTP de uma página da web para um servidor (seja ele interno ou externo).

Ele é uma API de baixo nível que fornece apenas o mecanismo básico para se realizar esses tipos de solicitações, de modo a retornar uma resposta para o desenvolvedor, que inclui:

  • A resposta em si,
  • Tratamento de erros,
  • Gerenciamento do estado da solicitação.

Durante a construção de uma aplicação em ReactJS, há duas formas de se fazer requisições HTTP:

1) Quando nosso componente precisa carregar algumas informações externas de primeira, ou seja, assim que ele é renderizado na tela:

import React, { useState, useEffect } from 'react';

const XMLHTTPRequest = () => {
 const [names, setNames] = useState([]);

 useEffect(() => {
 const xhr = new XMLHttpRequest();
 xhr.open('GET', 'https://jsonplaceholder.typicode.com/users', true);
 xhr.onload = function () {
 if (xhr.status >= 200 && xhr.status < 300) {
 const data = JSON.parse(xhr.responseText);
 setNames(data.map(user => user.name));
 } else {
 console.error('Erro ao buscar os nomes:', xhr.statusText);
 }
 };
 xhr.onerror = function () {
 console.error('Erro ao buscar os nomes:', xhr.statusText);
 };
 xhr.send();
 }, []);

 return (
 <>
 <h2>Lista de Nomes</h2>
 <ul>
 {names.map((name, index) => (
 <li key={index}>{name}</li>
 ))}
 </ul>
 </>
 );
};

export default XMLHTTPRequest;

Nesse caso podemos usar o useEffect em conjunto com o useState para reproduzir tal feito.

2) Quando o usuário precisa interagir com a nossa aplicação, para que posteriormente uma solicitação seja executada:

import React, { useState } from 'react';

const XMLHTTPRequest = () => {
 const [names, setNames] = useState([]);
 const [isLoading, setIsLoading] = useState(false);

 const fetchNames = () => {
 setIsLoading(true);
 const xhr = new XMLHttpRequest();
 xhr.open('GET', 'https://jsonplaceholder.typicode.com/users', true);
 xhr.onload = function () {
 setIsLoading(false);
 if (xhr.status >= 200 && xhr.status < 300) {
 const data = JSON.parse(xhr.responseText);
 setNames(data.map(user => user.name));
 } else {
 console.error('Erro ao buscar os nomes:', xhr.statusText);
 }
 };
 xhr.onerror = function () {
 setIsLoading(false);
 console.error('Erro ao buscar os nomes:', xhr.statusText);
 };
 xhr.send();
 };

 return (
 <>
 <h2>Lista de Nomes</h2>
 <button onClick={fetchNames} disabled={isLoading}>
 {isLoading ? 'Carregando...' : 'Carregar nomes'}
 </button>
 <ul>
 {names.map((name, index) => (
 <li key={index}>{name}</li>
 ))}
 </ul>
 </>
 );
};

export default XMLHTTPRequest;

No código acima o usuário precisa clicar no botão "Carregar nomes" para que a aplicação faça uma requisição HTTP a uma API externa.

Usando o XMLHttpRequest no ReactJS (POST)

Legal, até então nós vimos como recuperar informações de uma API externa usando o objeto XMLHttpRequest mais especificamente o método GET.

Mas como será o funcionamento do método POST? Ou seja, e se eu quiser enviar dados de um formulário usando o objeto XMLHttpRequest em uma aplicação feita com ReactJS?

Para isso, vamos criar um outro componente chamado de XMLHTTPRequestPOST.

XMLHTTPRequestPOST > index.jsx:

import React, {useEffect} from 'react';

const XMLHTTPRequestPOST = () => {
 return(
 <>
 <p>XMLHTTPRequestPOST!</p>
 </>
 )
}

export default XMLHTTPRequestPOST;

Agora vamos dar uma incrementada nesse código adicionando um formulário com um botão, que ao ser clicado vai enviar via método POST os dados do formulário:

XMLHTTPRequestPOST >index.jsx:  

import React, { useState } from 'react';

const XMLHTTPRequestPOST = () => {
 const [formData, setFormData] = useState({
 login: '',
 senha: '',
 nome: '',
 email: ''
 });
 const [isLoading, setIsLoading] = useState(false);

 const handleChange = (e) => {
 const { name, value } = e.target;
 setFormData({
 ...formData,
 [name]: value
 });
 };

 const handleSubmit = (e) => {
 e.preventDefault();
 setIsLoading(true);
 
 const xhr = new XMLHttpRequest();
 xhr.open('POST', 'https://jsonplaceholder.typicode.com/posts', true); // Usando JSONPlaceholder para teste
 xhr.setRequestHeader('Content-Type', 'application/json');
 xhr.onload = function () {
 setIsLoading(false);
 if (xhr.status >= 200 && xhr.status < 300) {
 console.log('Dados enviados com sucesso!');
 console.log('Resposta da API:', xhr.responseText);
 // Limpar o formulário após o envio bem-sucedido, se necessário
 } else {
 console.error('Erro ao enviar os dados:', xhr.statusText);
 }
 };
 xhr.onerror = function () {
 setIsLoading(false);
 console.error('Erro ao enviar os dados:', xhr.statusText);
 };
 xhr.send(JSON.stringify(formData));
 };

 return (
 <>
 <h2>Formulário de Registro</h2>
 <form onSubmit={handleSubmit}>
 <div>
 <label>Login:</label>
 <input type="text" name="login" value={formData.login} onChange={handleChange} required />
 </div>
 <div>
 <label>Senha:</label>
 <input type="password" name="senha" value={formData.senha} onChange={handleChange} required />
 </div>
 <div>
 <label>Nome:</label>
 <input type="text" name="nome" value={formData.nome} onChange={handleChange} required />
 </div>
 <div>
 <label>Email:</label>
 <input type="email" name="email" value={formData.email} onChange={handleChange} required />
 </div>
 <button type="submit" disabled={isLoading}>
 {isLoading ? 'Enviando...' : 'Enviar'}
 </button>
 </form>
 </>
 );
};

export default XMLHTTPRequestPOST;

Após preencher o formulário a API vai retornar os mesmos dados que enviamos em conjunto com um id:

Isso significa que o envio foi um sucesso 🙂

Usando o Fetch API no ReactJS (GET)

Agora chegou o momento de explorarmos uma outra biblioteca que pertence ao Javascript, que a grosso modo, tem uma utilização mais fácil se comparada ao XMLHttpRequest.

Seu nome é Fetch API, e ela é uma interface moderna para realizar requisições HTTP no navegador web, fornecendo uma maneira mais fácil e simplificada de fazer solicitações AJAX (Asynchronous JavaScript and XML) se comparado do ao antigo XMLHttpRequest (XHR).

A Fetch API utiliza Promises, o que simplifica o tratamento de requisições assíncronas e torna o código mais legível e fácil de entender.

Para começar, vamos criar um novo componente chamado de FecthAPIGET dentro da pasta components.

FetchAPIGET > index.jsx:

import React, {useEffect} from 'react';

const FetchAPIGET = () => {
 return(
 <>
 <p>FetchAPIGET!</p>
 </>
 )
}

export default FetchAPIGET;

Com isso, há duas formas de se fazer requisições em aplicações do ReactJS usando o Fetch API:

1) Quando nosso componente precisa carregar algumas informações externas de primeira, ou seja, assim que ele é renderizado na tela:

import React, { useState, useEffect } from 'react';

const FetchAPIGET = () => {
 const [names, setNames] = useState([]);
 const [isLoading, setIsLoading] = useState(true);

 useEffect(() => {
 const fetchNames = async () => {
 try {
 const response = await fetch('https://jsonplaceholder.typicode.com/users');
 if (!response.ok) {
 throw new Error('Erro ao buscar os nomes: ' + response.statusText);
 }
 const data = await response.json();
 setNames(data.map(user => user.name));
 setIsLoading(false);
 } catch (error) {
 setIsLoading(false);
 console.error(error);
 }
 };

 fetchNames();
 }, []);

 return (
 <>
 <h2>Lista de Nomes</h2>
 {isLoading ? (
 <p>Carregando...</p>
 ) : (
 <ul>
 {names.map((name, index) => (
 <li key={index}>{name}</li>
 ))}
 </ul>
 )}
 </>
 );
};

export default FetchAPIGET;

Onde nesse caso podemos usar o useEffect em conjunto com o useState para reproduzir tal feito.  

2) Quando o usuário precisa interagir com a nossa aplicação, para que posteriormente uma solicitação seja executada:

import React, { useState } from 'react';

const FetchAPIGET = () => {
 const [names, setNames] = useState([]);
 const [isLoading, setIsLoading] = useState(false);

 const fetchNames = () => {
 setIsLoading(true);
 fetch('https://jsonplaceholder.typicode.com/users')
 .then(response => {
 setIsLoading(false);
 if (!response.ok) {
 throw new Error('Erro ao buscar os nomes: ' + response.statusText);
 }
 return response.json();
 })
 .then(data => {
 setNames(data.map(user => user.name));
 })
 .catch(error => {
 setIsLoading(false);
 console.error(error);
 });
 };

 return (
 <>
 <h2>Lista de Nomes</h2>
 <button onClick={fetchNames} disabled={isLoading}>
 {isLoading ? 'Carregando...' : 'Carregar nomes'}
 </button>
 <ul>
 {names.map((name, index) => (
 <li key={index}>{name}</li>
 ))}
 </ul>
 </>
 );
};

export default FetchAPIGET;

No código acima o usuário precisa clicar no botão "Carregar nomes" para que a aplicação faça uma requisição.  

Usando o Fetch API no ReactJS (POST)

Para executar requisições via método POST também é fácil 🙂

Supondo que temos um formulário, e que desejamos enviar tais dados para uma API externa, você pode fazer isso da seguinte forma:

FetchAPIPOST > index.jsx:

import React, { useState } from 'react';

const FetchAPIPOST = () => {
 const [formData, setFormData] = useState({
 login: '',
 senha: '',
 nome: '',
 email: ''
 });
 const [isLoading, setIsLoading] = useState(false);

 const handleChange = (e) => {
 const { name, value } = e.target;
 setFormData({
 ...formData,
 [name]: value
 });
 };

 const handleSubmit = (e) => {
 e.preventDefault();
 setIsLoading(true);

 fetch('https://jsonplaceholder.typicode.com/posts', {
 method: 'POST',
 headers: {
 'Content-Type': 'application/json'
 },
 body: JSON.stringify(formData)
 })
 .then(response => {
 setIsLoading(false);
 if (!response.ok) {
 throw new Error('Erro ao enviar os dados: ' + response.statusText);
 }
 console.log('Dados enviados com sucesso!');
 console.log('Resposta da API:', response.json());
 // Limpar o formulário após o envio bem-sucedido, se necessário
 })
 .catch(error => {
 setIsLoading(false);
 console.error(error);
 });
 };

 return (
 <>
 <h2>Formulário de Registro</h2>
 <form onSubmit={handleSubmit}>
 <div>
 <label>Login:</label>
 <input type="text" name="login" value={formData.login} onChange={handleChange} required />
 </div>
 <div>
 <label>Senha:</label>
 <input type="password" name="senha" value={formData.senha} onChange={handleChange} required />
 </div>
 <div>
 <label>Nome:</label>
 <input type="text" name="nome" value={formData.nome} onChange={handleChange} required />
 </div>
 <div>
 <label>Email:</label>
 <input type="email" name="email" value={formData.email} onChange={handleChange} required />
 </div>
 <button type="submit" disabled={isLoading}>
 {isLoading ? 'Enviando...' : 'Enviar'}
 </button>
 </form>
 </>
 );
};

export default FetchAPIPOST;

Note que a Fetch API retorna uma promise que contém os dados que enviamos a API externa:

Simples, rápido e fácil 🤓

Usando o Axios em requisições HTTP

O Axios é uma biblioteca feita em Javascript bastante popular e utilizada pela comunidade, sendo capaz de realizar solicitações HTTP funcionando muito bem com aplicações em ReactJS.

O Axios facilita o envio de solicitações HTTP de forma assíncrona para endpoints do tipo REST e que fazem a execução de operações CRUD (Create, Read, Update e Delete), bem como o tratamento dessas respostas.

Para utilizar o Axios em seu aplicativo em ReactJS, primeiro você precisa instalá-lo dentro da pasta raiz do seu projeto.

Para isso abra o terminal dentro da pasta raiz do seu projeto em ReactJS, e execute o seguinte comando:

npm install axios

Após a instalação, basta importá-lo para dentro do componente (ou service) que você deseja utilizar.

Usando o Axios no ReactJS (GET)

Antes de usar o Axios, vamos criar um novo componente dentro da pasta components chamado de AxiosGET.

AxiosGET > index.jsx:

import React, {useEffect} from 'react';

const AxiosGET = () => {
 return(
 <>
 <p>AxiosGET!</p>
 </>
 )
}

export default AxiosGET;

Assim como vimos no Fetch API e também no XMLHttpRequest, existem duas formas de se utilizar o Axios.

1) Quando nosso componente precisa carregar algumas informações externas de primeira, ou seja, assim que ele é renderizado na tela:

import React, { useState, useEffect } from 'react';
import axios from 'axios';

const AxiosGET = () => {
 const [names, setNames] = useState([]);
 const [isLoading, setIsLoading] = useState(true);

 useEffect(() => {
 const fetchData = async () => {
 try {
 const response = await axios.get('https://jsonplaceholder.typicode.com/users');
 if (response.status !== 200) {
 throw new Error('Erro ao buscar os nomes: ' + response.statusText);
 }
 setNames(response.data.map(user => user.name));
 setIsLoading(false);
 } catch (error) {
 setIsLoading(false);
 console.error(error);
 }
 };

 fetchData();
 }, []);

 return (
 <>
 <h2>Lista de Nomes</h2>
 {isLoading ? (
 <p>Carregando...</p>
 ) : (
 <ul>
 {names.map((name, index) => (
 <li key={index}>{name}</li>
 ))}
 </ul>
 )}
 </>
 );
};

export default AxiosGET;

2) Quando o usuário precisa interagir com a nossa aplicação, para que posteriormente uma solicitação seja executada:

import React, { useState, useEffect } from 'react';
import axios from 'axios';

const AxiosGET = () => {
 const [names, setNames] = useState([]);
 const [isLoading, setIsLoading] = useState(false);

 const fetchNames = async () => {
 setIsLoading(true);
 try {
 const response = await axios.get('https://jsonplaceholder.typicode.com/users');
 if (response.status >= 200 && response.status < 300) {
 setNames(response.data.map(user => user.name));
 } else {
 console.error('Erro ao buscar os nomes:', response.statusText);
 }
 } catch (error) {
 console.error('Erro ao buscar os nomes:', error.message);
 }
 setIsLoading(false);
 };

 return (
 <>
 <h2>Lista de Nomes</h2>
 <button onClick={fetchNames} disabled={isLoading}>
 {isLoading ? 'Carregando...' : 'Carregar nomes'}
 </button>
 <ul>
 {names.map((name, index) => (
 <li key={index}>{name}</li>
 ))}
 </ul>
 </>
 );
};

export default AxiosGET;

Usando o Axios no ReactJS (POST)

Considerando que temos um formulário e que queremos enviar as informações dele para uma API externa usando o Axios, podemos fazer isso da seguinte forma:

AxiosPOST > index.jsx:

import React, { useState } from 'react';
import axios from 'axios';

const AxiosPOST = () => {
 const [formData, setFormData] = useState({
 login: '',
 senha: '',
 nome: '',
 email: ''
 });
 const [isLoading, setIsLoading] = useState(false);

 const handleChange = (e) => {
 const { name, value } = e.target;
 setFormData({
 ...formData,
 [name]: value
 });
 };

 const handleSubmit = async (e) => {
 e.preventDefault();
 setIsLoading(true);

 try {
 const response = await axios.post('https://jsonplaceholder.typicode.com/posts', formData);
 if (response.status >= 200 && response.status < 300) {
 console.log('Dados enviados com sucesso!');
 console.log('Resposta da API:', response.data);
 // Limpar o formulário após o envio bem-sucedido, se necessário
 } else {
 throw new Error('Erro ao enviar os dados: ' + response.statusText);
 }
 } catch (error) {
 console.error(error);
 }

 setIsLoading(false);
 };

 return (
 <>
 <h2>Formulário de Registro</h2>
 <form onSubmit={handleSubmit}>
 <div>
 <label>Login:</label>
 <input type="text" name="login" value={formData.login} onChange={handleChange} required />
 </div>
 <div>
 <label>Senha:</label>
 <input type="password" name="senha" value={formData.senha} onChange={handleChange} required />
 </div>
 <div>
 <label>Nome:</label>
 <input type="text" name="nome" value={formData.nome} onChange={handleChange} required />
 </div>
 <div>
 <label>Email:</label>
 <input type="email" name="email" value={formData.email} onChange={handleChange} required />
 </div>
 <button type="submit" disabled={isLoading}>
 {isLoading ? 'Enviando...' : 'Enviar'}
 </button>
 </form>
 </>
 );
};

export default AxiosPOST;

A diferença é que o axios nos retorna a lista de objetos diretamente, observe:

Incrível não? 🤩

Criando um serviço HTTP modular usando o Axios

Se você passou pela aula que fala sobre Trabalhando com Serviços.

Talvez tenha se sentido tentado a "servicializar tudo", não é verdade?

Pois é mais fácil, organizado e ajuda a sua equipe de desenvolvimento.

A verdade é que com a crescente alta de aplicações modernas, garantir a modularidade, flexibilidade e manutenibilidade é crucial para a escalabilidade das nossas aplicações.

Uma maneira fácil de atingir tal feito é por meio do uso de padrões de design de maneira eficaz, e nada melhor do que utilizar o padrão Facade para criar um módulo separado de requisições HTTP.

O que é o padrão Facades?

O Facade Pattern é uma interface simplificada que podemos usar para criar diversos subsistemas mais complexos.

Então, em vez de fazer solicitações HTTP com diferentes cabeçalhos e configurações dentro de diversos componentes na sua aplicação, você cria um serviço centralizado (HttpService ou HttpInstance) que visa esconder toda aquela complexidade de cabeçalhos e configurações.

Isso ajuda a reduzir possíveis erros, pois a partir de agora nós temos uma lógica centralizada, de modo que se der erro em alguma requisição, sabemos qual arquivo procurar.

Além é claro, de ajudar a melhorar a legibilidade, manutenção do código, fornencendo uma flexibilidade para alterarmos (no futuro) a biblioteca subjacente (axios) sem afetar o restante do aplicativo.

Criando seu módulo HTTP passo a passo

Antes de criar seu primeiro módulo de requisições HTTP - que eu acredito que você vai usá-lo daqui em diante em diversos projetos pessoas 😆 - você precisa garantir que a biblioteca Axios está instalada na sua aplicação.

npm install axios

Em seguida basta seguir o passo a passo abaixo.

Passo 1) Crie um arquivo chamado httpService.js dentro da pasta services.

Passo 2) Crie a estrutura básica do arquivo httpService.js:

import axios from 'axios';

class HttpService {
 constructor(baseURL = 'https://api.yourdomain.com') {
 this.baseUrl = baseURL;
 this.instance = axios.create({ baseURL: this.baseUrl });
 }
}

Perceba que criamos uma nova classe chamada de HttpService com um método construtor que será responsável por receber a URL base que vai conter os endpoints da API.

Passo 3) Criar um método para o cabeçalho:

get defaultHeaders() {
 return {
 'Authorization': localStorage.getItem('Authorization'),
 'Content-Type': 'application/json',
 };
}

É crucial que você configure um método responsável por lidar com tokens de autenticação e outros cabeçalhos de maneira eficaz.

Passo 4) Crie um método de solicitação principal:

async request(method, url, data = null, customHeaders = {}) {
 const headers = { ...this.defaultHeaders, ...customHeaders };
 const source = axios.CancelToken.source();
 
 const config = {
 method,
 url,
 headers,
 cancelToken: source.token
 };
 
 if (data) {
 config.data = data;
 }
 
 return {
 request: this.instance(config),
 cancel: source.cancel
 };
 }

Em vez de repetir a lógica para get, post, e etc... você cria um método principal capaz de trabalhar com todos os metodos HTTP.

Passo 5) Definindo métodos específicos para diferentes tipos de requisição:

get(url, customHeaders = {}) {
 return this.request('get', url, null, customHeaders);
}

post(url, data, customHeaders = {}) {
 return this.request('post', url, data, customHeaders);
}

put(url, data, customHeaders = {}) {
 return this.request('put', url, data, customHeaders);
}

delete(url, customHeaders = {}) {
 return this.request('delete', url, null, customHeaders);
}

Quando um componente chamar a classe HttpService, ele sempre executará um desses métodos, de modo a passar a URL, dados ou algum cabeçalho customizado caso houver.

Por de baixo dos panos, a própria classe se encarregará de chamar o método principal (request()) e fazer com que as solicitações HTTP aconteçam.

Passo 6) Criando um método de ajuste de URL base:

setBaseUrl(newUrl) {
 this.baseUrl = newUrl;
 this.instance.defaults.baseURL = newUrl;
}

Haverão momentos em que a sua aplicação irá precisar mudar a URL base que foi setada no método construtor da classe, e nada melhor do que chamar um método em vez de ter que instanciar a classe novamente, não é verdade? 😇

Abaixo você encontra o código completo do arquivo httpService.js:

import axios from 'axios';

class HttpService {
 constructor(baseURL = 'https://api.yourdomain.com') {
 this.baseUrl = baseURL;
 this.instance = axios.create({ baseURL: this.baseUrl });
 }

 get defaultHeaders() {
 return {
 'Authorization': localStorage.getItem('Authorization'),
 'Content-Type': 'application/json',
 };
 }

 async request(method, url, data = null, customHeaders = {}) {
 const headers = { ...this.defaultHeaders, ...customHeaders };
 const source = axios.CancelToken.source();
 
 const config = {
 method,
 url,
 headers,
 cancelToken: source.token
 };
 
 if (data) {
 config.data = data;
 }
 
 return {
 request: this.instance(config),
 cancel: source.cancel
 };
 }

 get(url, customHeaders = {}) {
 return this.request('get', url, null, customHeaders);
 }
 
 post(url, data, customHeaders = {}) {
 return this.request('post', url, data, customHeaders);
 }
 
 put(url, data, customHeaders = {}) {
 return this.request('put', url, data, customHeaders);
 }
 
 delete(url, customHeaders = {}) {
 return this.request('delete', url, null, customHeaders);
 }

 setBaseUrl(newUrl) {
 this.baseUrl = newUrl;
 this.instance.defaults.baseURL = newUrl;
 }
}

export default HttpService;

Chamando o módulo HttpService em um componente

Para fazer uma requisição usando o módulo que acabamos de criar, é bem simples, observe:

MeuComponenteGET > index.jsx:

import React, { useState } from 'react';
import HttpService from '../../services/httpService';

const httpService = new HttpService();

const MeuComponenteGET = () => {
 const [names, setNames] = useState([]);
 const [isLoading, setIsLoading] = useState(false);

 const fetchNames = async () => {
 setIsLoading(true);
 try {
 const { request } = await httpService.get('https://jsonplaceholder.typicode.com/users');
 const response = await request;
 if (response.status >= 200 && response.status < 300) {
 setNames(response.data.map(user => user.name));
 } else {
 console.error('Erro ao buscar os nomes:', response.statusText);
 }
 } catch (error) {
 console.error('Erro ao buscar os nomes:', error.message);
 }
 setIsLoading(false);
 };

 return (
 <>
 <h2>Lista de Nomes (HttpService)</h2>
 <button onClick={fetchNames} disabled={isLoading}>
 {isLoading ? 'Carregando...' : 'Carregar nomes'}
 </button>
 <ul>
 {names.map((name, index) => (
 <li key={index}>{name}</li>
 ))}
 </ul>
 </>
 );
};

export default MeuComponenteGET;

Também é possível enviar um formulário via método POST da seguinte forma:

MeuComponentePOST > index.jsx:

import React, { useState } from 'react';
import HttpService from '../../services/httpService';

const httpService = new HttpService();

const MeuComponentePOST = () => {
 const [formData, setFormData] = useState({
 login: '',
 senha: '',
 nome: '',
 email: ''
 });
 const [isLoading, setIsLoading] = useState(false);

 const handleChange = (e) => {
 const { name, value } = e.target;
 setFormData({
 ...formData,
 [name]: value
 });
 };

 const handleSubmit = async (e) => {
 e.preventDefault();
 setIsLoading(true);

 try {
 const { request } = await httpService.post('https://jsonplaceholder.typicode.com/posts', formData);
 const response = await request;
 if (response.status >= 200 && response.status < 300) {
 console.log('Dados enviados com sucesso!');
 console.log('Resposta da API:', response.data);
 // Limpar o formulário após o envio bem-sucedido, se necessário
 } else {
 console.error('Erro ao enviar os dados:', response.statusText);
 }
 } catch (error) {
 console.error('Erro ao enviar os dados:', error.message);
 }

 setIsLoading(false);
 };

 return (
 <>
 <h2>Formulário de Registro (HttpService)</h2>
 <form onSubmit={handleSubmit}>
 <div>
 <label>Login:</label>
 <input type="text" name="login" value={formData.login} onChange={handleChange} required />
 </div>
 <div>
 <label>Senha:</label>
 <input type="password" name="senha" value={formData.senha} onChange={handleChange} required />
 </div>
 <div>
 <label>Nome:</label>
 <input type="text" name="nome" value={formData.nome} onChange={handleChange} required />
 </div>
 <div>
 <label>Email:</label>
 <input type="email" name="email" value={formData.email} onChange={handleChange} required />
 </div>
 <button type="submit" disabled={isLoading}>
 {isLoading ? 'Enviando...' : 'Enviar'}
 </button>
 </form>
 </>
 );
};

export default MeuComponentePOST;

Veja como ficou o retorno no console:

Chamando o módulo HttpService de uma maneira limpa

Com a classe HttpService nós também podemos fazer chamadas mais limpas, como por exemplo:

const httpService = new HttpService();

//Coloque o comando abaixo em algum canto do seu componente
apiService.get('/getUsers').request.then(data => {
 //Trabalha com o retorno aqui...
}).catch(error => {
 //Trate possíveis erros aqui...
});

Entendendo o Request e o Cancel

Embora os métodos de solicitações que criamos sejam simples, a funcionalidade de cancelamento (cancel) merece um pouco de destaque.

Porque cancelar requisições HTTP?

Em aplicações da web, especialmente aquelas que fazem muitas requisições, haverão cenários em que uma solicitação HTTP contínua pode se tornar redudante e bastante custosa, de modo que não valha a pena continuar esperando a finalização de tal requisição.

Como por exemplo, o seu usuário pode sair da janela atual ou iniciar uma nova solicitação que torne a anterior desnecessária.

Nestes casos, ter a capacidade de abortar a solicitação anterior pode economizar valiosos recursos de rede e evitar efeitos colaterais inesperados.

Já a nossa classe HttpService utiliza o recurso de token de cancelamento do Axios, que é conceitualmente semelhante ao AbortController da API Fetch.

Onde a ideia é fornecer um método de cancelamento associado junto com cada solicitação, e se necessário, podemos invocar esse método de cancelamento de forma a anular a solicitação HTTP 😁

Melhorando o nosso HttpService

Apesar do nosso módulo funcionar como o esperado, é certo dizer que a sua arquitetura foi projetada para escalabilidade.

Entretanto, podemos aprimorar ainda mais o nosso HttpService de modo a:

  • Criar uma funcionalidade capaz de definir tempo limite de solicitação.
  • Criar interceptadores de modo a obter insights mais aprofundados sobre solicitações e respostas antes de serem processadas e enviadas.
  • Criar uma estrutura unificada de tratamento de erros.
  • Criar métodos dinâmicos para modificar cabeçalhos durante o ciclo de vida do aplicativo.

Graças ao padrão Facades o nosso HttpService permanece adaptável e pronto para receber melhorias sem alterar a esturutra do nosso aplicativo.

Arquivos da lição

Os arquivos dessa lição podem ser encontrados neste repositório do GitHub.

Conclusão

Nesta lição você aprendeu a fazer diversas requisições HTTP usando o XMLHttpRequest, Fetch API e o Axios.

Aprendendo também a criar seu próprio módulo de requisições usando o padrão de design Facades.

Até a próxima!