Meu primeiro Higher-Order Component — Parte 2
Continuação do artigo que ensina os fundamentos de componentização na prática através do React. Desta vez implementando requisições à API do GitHub ou o Mocky, aproveitando o HOC de loading que já havíamos criado. Também mostra na prática a aplicação de várias features do ES6+, como async/await, destructuring, spread etc.
Olá galera! E aí, como vão os bolões da Copa? Espero que estejam faturando bem ˆˆ
Dando continuação ao artigo sobre HOCs, aproveitaremos a implementação anterior para fazermos algo mais avançado. Caso você não tenha visto o artigo anterior, segue o link abaixo:
Nesta parte 2 aprenderemos a:
- Isolar requisições à API em um HOC, separando sua lógica e deixando os componentes mais puros
- Como usar múltiplos HOCs (no caso, 2) em um mesmo componente
Implementando a segunda parte
Caso você não tenha feito o tutorial anterior, há dois caminhos que recomendo a seguir antes de começar a parte 2, que estão no pré-requisito abaixo.
1. Pré-requisito
- Fazer o tutorial da parte 1 em https://medium.com/reactbrasil/meu-primeiro-higher-order-component-a376efc654a8 ou
- Clonar e instalar o projeto do repositório https://github.com/rafaelmaruta/create-react-hoc-app
2. Configure uma lib para requisições AJAX
Usarei uma lib chamada Isomorphic Fetch para fazer requisições AJAX, mas você é livre para optar por fazer AJAX da forma que desejar.
Rode em seu terminal no diretório onde estão o package.json e o node_modules:
npm i isomorphic-fetch -D
Usando Yarn:
yarn add isomorphic-fetch --dev
3. Criar o HOC fetchAPI
Na pasta ./hocs crie um arquivo fetchAPI.js com o seguinte conteúdo:
Assim como o HOC withLoading, este HOC é uma função que retorna outro componente, mas com vários detalhes a mais. Explicarei estes detalhes por linha:
- Linha 2: importação da lib Isomorphic Fetch.
- Linha 4: função iniciada já com exportação, que será importada em outro arquivo onde receberá um componente como parâmetro para receber novas funcionalidades. É importante reparar que ela não recebe somente o componente, mas também a url da API.
- Linha 5: criação com retorno do novo componente (de classe) que substituirá o componente previamente recebido como parâmetro.
- Linha 6 à 8: initial state que receberá o resultado do fetch e evoluirá o estado da aplicação.
- Linha 10 à 18: lifecycle onde será feito o fetch usando o Isomorphic Fetch que importamos na linha 2, usando a url recebida como parâmetro na linha 4. É importante repararmos que usamos async/await para facilitar, deixando uma sintaxe limpa. O try/catch pode ser usado para lidar com erros em tratamentos assíncronos do async/await. Tivemos que usar o await duas vezes pois na primeira vez o fetch retorna outra promise, que deve ser resolvida novamente com um segundo await.
- Linha 20 à 26: método que renderiza o novo componente, que é o componente recebido como parâmetro na linha 4 e que recebe uma nova prop
data
com o valor do statedata
.
4. Alterar os componentes para receberem a HOC fetchAPI
Agora devemos importar a HOC fetchAPI nos arquivos Infos.js, ReposList.js e StarredList.js, além de preparar o componente para receber os dados dinâmicamente via prop, definindo suas PropTypes e DefaultProps. O componente Infos ficará assim:
./src/components/Infos.js
Agora vamos às mudanças!
- Linha 3: importação do HOC fetchAPI.
- Linha 6: a URL da API que será consultada. Você pode usar a API pública do GitHub, mas conforme dito aqui, há um limite de 60 requisições por hora para requisições não autenticadas, por isso eu copiei o result do request e coloquei no Mocky. Você pode fazer o mesmo. A rota da API do GitHub usada para este componente é https://api.github.com/users/{username}
- Inicialização do componente, fazendo destructuring arguments para receber somente a prop
data
. - Linha 9: Faz destructuring object das propriedades do objeto
data
, que é uma prop recebida do componente pai (o withLoading) e que carrega o payload da requisição. - Linhas 16 e 20 à 22: marcações onde os valores recebidos da requisição serão preenchidos.
- Linhas 27 à 38: PropTypes e DefaultProps da prop
data
. - Linhas 40 e 42: O nosso componente Infos recebendo o HOC withLoading como Higher-Order Component, cujo componente retornado receberá outro HOC na linha 42, passando a url da API como segundo parâmetro, e sendo exportado. Estas linhas são a mesma coisa que:
export default fetchAPI(withLoading(Infos), apiURL);
Caso você não esteja entendendo muito bem o processo todo que está acontecendo aqui, não se preocupe pois eu explicarei melhor adiante. Agora vamos ao componente ReposList.
./src/components/ReposList.js
As mudanças neste componente foram praticamente as mesmas que o componente Infos. A única diferença mesmo é da linha 11 à 21, pois o result da requisição é do tipo Array então temos que iterar por ele com map. O mesmo pode ser dito sobre o componente StarredList:
./src/components/StarredList.js
As rotas das APIs usadas de ambas são:
- https://api.github.com/users/{username}/repos
- https://api.github.com/users/{username}/starred
Com isto, terminamos nossas mudanças, agora vou tentar explicar o processo todo. Antes os nossos componentes recebiam apenas um HOC, o withLoading, que é um componente que interceptava a chamada do componente visual, checando se a prop data
é válida. Enquanto não for válida, ele mesmo trata de exibir o loading, até o momento em que a prop se torna válida e exibe o componente. Porém agora neste novo caso há a interceptação de um novo HOC, o fetchAPI, que inclusive intercepta antes da chamada do withLoading pois colocamos o fetchAPI como HOC do retorno do withLoading. A hierarquia de componentes ficará mais ou menos assim:
App
|
|---> fetchAPI ---> withLoading ---> Infos
|
|---> fetchAPI ---> withLoading ---> ReposList
|
|---> fetchAPI ---> withLoading ---> StarredList
Primeiramente será feita uma chamada à rota da API correspondente ao componente no HOC fetchAPI, e aí enquanto o withLoading não receber o payload, ele tratará de ficar exibindo o loading. Quando o fetchAPI repassar o payload para o withLoading, ele passará a exibir o componente (Infos, ReposList ou StarredList) repassando o payload a ele para que ele seja preenchido.
Um questionamento que você pode ter é: Por que não usar componentes comuns (stateful ou stateless) em vez de HOCs? É porque esses HOCs, que também são componentes, possuem um tratamento mais lógico do que visual, facilitando não só a abstração de código, mas também separando a parte lógica da parte visual da aplicação. Como você deve ter notado, também isola uma lógica de outra com o uso de múltiplos HOCs.
O resultado em http://localhost:3000/ que você obterá deverá ser parecido com isto:
Outra coisa que você pode ter notado, é que os componentes ReposList e StarredList são semelhantes, então poderíamos usar um único componente para ambos os casos.
5. Melhorando a componentização do ReposList e StarredList
A primeira coisa que devemos analisar é o que há de diferente nos componentes. No caso são 3 coisas:
- A rota da API
- O título do componente
- O nome do componente
Com relação às rotas e os títulos, podemos removê-los dos componentes e passá-los para o componente App, para depois serem passados via prop para os componentes, ficando assim:
Agora podemos remover um dos componentes ReposList ou StarredList e renomear o outro simplesmente para List:
Agora, as mudanças:
- Renomear o nome do arquivo para List.js.
- Remoção da linha que continha a
const apiURL
. - Linha 6: Agora o componente se chama simplesmente List e também recebe a prop
title
. - Linha 8: É onde a prop
title
agora é chamada. - Linhas 23, 28 e 37: Troca do nome do componente para List.
- Linha 39: Remoção do segundo parâmetro da HOC apiURL, que agora é definida e passada pelo componente App.
Agora devemos fazer algumas mudanças nos HOCs também, já que alteramos as formas como o title
e o apiURL
são passados para os componentes:
Como a apiURL não é mais um parâmetro recebido e sim uma prop, as mudanças que fizemos foram as seguintes:
- Linha 5: remoção do segundo argumento
apiURL
, ficando apenas o MyComponent - Linhas 11 à 17: PropTypes e DefaultProps do
apiURL
. - Linha 21:
apiURL
sendo obtido das props. - Linha 33: repasse da prop
title
adiante, já que o fetchAPI intercepta a passagem das props também.
O seguinte HOC que intercepta é o withLoading, que recebe as props data
e agora recebe também o title
. Para que na linha 7 não tenhamos que repassar as props data
e também o title
individualmente, é mais fácil repassar todas as props através de spread object, pois todas as props que o withLoading recebe devem ser repassadas ao componente seguinte. Por isso modificamos o parâmetro da linha 5 para receber o objeto props
todo, para fazer spread dele na linha 7, e modificando a linha 6 para ler a prop data
a partir do objeto props
.
Com isto, terminamos nossa abstração.
Na próxima parte mostrarei como aplicar:
São 3 formas diferentes de se usar o conceito de Higher-Order.
Conclusão
Como vocês devem ter notado, além de tratar de HOCs de uma forma mais avançada, passamos por vários conceitos de JavaScript, ES6+ como destructuring, spread, async/await etc. Para tirar proveito do React é muito importante se aprofundar em JavaScript e em suas novidades, pois eles quebram um galhão, surgem apenas para facilitar as nossas vidas.
Segue abaixo os links tanto da versão com ReposList e StarredList quanto a versão abstraída com o List (que está em uma branch):
- https://github.com/rafaelmaruta/create-react-hoc-fetch-app
- https://github.com/rafaelmaruta/create-react-hoc-fetch-app/tree/abstract-components
Em caso de dúvidas, sugestões, considerações etc. sintam-se a vontade para comentar!
Obrigado! Abraços!