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.

Imagem emprestada de https://blog.rocketseat.com.br/higher-order-components-hocs-no-react-e-react-native/

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

  1. Fazer o tutorial da parte 1 em https://medium.com/reactbrasil/meu-primeiro-higher-order-component-a376efc654a8 ou
  2. 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 state data.

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:

./src/App.js

Agora podemos remover um dos componentes ReposList ou StarredList e renomear o outro simplesmente para List:

./src/components/List.js

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:

./src/hocs/fetchAPI.js

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.
./src/hocs/withLoading.js

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):

Em caso de dúvidas, sugestões, considerações etc. sintam-se a vontade para comentar!

Obrigado! Abraços!