Skip to main content

ReactJS Hooks - use Experimental Hook

· 10 min read
Bruno Carneiro
Fundador da @TautornTech

ReactJS Hooks use [Experimental]

Olá jovens gafanhotos, seguindo a trilha de artigos a respeito dos Hooks, neste artigo irei falar sobre o use. Um Hook que está em fase experimental e deve estar disponível em breve no React.

Esse hook pode ajudar bastante o nosso dia-a-dia porque atualmente a quantidade de código escrito para resolver uma promise é bem grande. Mas não apenas isso.

E para demonstrar como o hook funciona realizando uma comparação com o estado atual eu criei um projeto simples demonstrando algunas cartas do jogo Hearthstone.

Projeto final

tip

Caso queira saber um pouco mais sobre os Hooks, você pode ler este conteúdo aqui: React Hooks, Introdução

 Conheça também como funciona o hook useEffect

O que é RFC?

Mas antes de iniciarmos os exemplos preciso explicar o que é RFC.

RFC significa Request for Comments, ou seja, existe uma solitação para comentários sobre um determinado assunto. Uma RFC é um documento técnico onde várias pessoas podem discutir sobre uma determinada tecnologia, métodos, pesquisas, ideias e etc. No caso do React existe uma RFC aberta discutindo a respeito do suporte de First Class Support para Promises e métodos com async/await.

Essa RFC traz uma versão experimental do React para que a comunidade possa utilizar o suporte de resolução de uma promise javascript utilizando o Suspense do React. Algo que já é aguardado há algum tempo.

O que a RFC de adição de First Class Support irá trazer:

  • Suporte para resolução de async/await em componentes do lado do servidor. Escrita de components do lado do Servidor utilizando a sintaxe JavaScript await definindo o seu componente como uma função assíncrono.
  • Introdução do hook use. Assim como o await, o use vai ser utilizado com promises, podendo ser utilizando normalmente com components e Hooks, até mesmo do lado do Client.

Isso permite que desenvolvedores React acessem dados de requisições assíncronas com o a api Suspense do React.

Texto completo

Importante, como essa versão ainda está em modo experimental e existe uma discução a respeito dela então mudanças podem surgir, então aguarde a versão final antes de utilizá-la em produção :)

Hands on

Agora vou demonstrar algumas vantagens que o hook use poderá trazer no desenvolvimento com React.

Utilizei o Vite para criar a estrutura inicial do projeto, para isso excute os comandos abaixos:

$ yarn create vite

Informe o nome do projeto ou tecle enter (chamei de reactjs-use-hook)

  • ReactJS
  • TypeScript

// Utilizando Yarn

$ cd reactjs-use-hook
$ yarn add react@experimental react-dom@experimental

ou

// Utilizando NPM

$ cd reactjs-use-hook
$ npm install react@experimental react-dom@experimental

Abra o arquivo tsconfig.json e adicione a opção "types": ["react/experimental"] para que o TypeScript reconheça o novo Hook.

// tsconfig.json
{
"compilerOptions": {
// outras opções
"types": ["react/experimental"]
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}

note

Para o desenvolvimento deste projeto utilizei uma api pública do Hearthstone.

Agora basta executar o comando abaixo para iniciar o projeto:

$ yarn dev

Logo, para fins de comparação, vou criar duas versões deste projeto, a primeira utilizando useEffect e useState (CurrentVersion.tsx) para controlar toda a chamada assíncrona e a segunda com hook use (ExperimentalVersion.tsx).

Estrutura de como ficou o projeto final:

caution

Não adicionarei neste tutorial a estrutura de arquivos .css e os assets utilizados.

Os arquivos estão disponíveis neste repositório reactjs-use-hook caso queira reproduzir o projeto por inteiro.

Para iniciar crie um arquivo .env na raíz da pasta contendo as chaves da api do HearthStone.

VITE_RAPID_API_KEY=sua-api-key
VITE_RAPID_API_HOST=sua-api-host

Obs.: Não utilizei as minhas chaves que estão no repositório porque elas possuem limite de uso diário e/ou podem ficar inválidas. Então recomendo fortemente que para reprodução deste projeto utilize as suas próprias chaves.

Logo após isso modifique o arquivo App.tsx, ficando desta maneira:

// App.tsx

import { useState } from "react"
import logo from './assets/logo.png'
import ExperimentalVersion from './ExperimentalVersion'
import CurrentVersion from './CurrentVersion'
import './App.css'

function App() {
const [version, setVersion] = useState<string>('experimental') // escolha da versão
const [cardClass, setCardClass] = useState<string>('warlock') // escolha da classe das cartas

return (
<div className="App">
<div>
<a href="https://tautorn.com.br" target="_blank">
<img src={logo} className="logo tautorn tech" alt="Tautorn Tech Logo" title="Tautorn Tech Logo" />
</a>

<div>
<button onClick={() => setVersion('current')}>Current Version</button>
<button onClick={() => setVersion('experimental')}>Experimental Version</button>
</div>

<div style={{ margin: '2rem'}}> <!-- Não criei classe apenas por comodismo deste exemplo, mas não crie css inline-->
<button onClick={() => setCardClass('warlock')}>Warlock</button>
<button onClick={() => setCardClass('druid')}>Druid</button>
<button onClick={() => setCardClass('mage')}>Mage</button>
<button onClick={() => setCardClass('warrior')}>Warrior</button>
</div>

{version === 'current' && (<CurrentVersion cardClass={cardClass} />)}
{version === 'experimental' && (<ExperimentalVersion cardClass={cardClass} />)}

</div>
</div>
)
}

export default App

No arquivo acima utilizo duas importações import ExperimentalVersion from './ExperimentalVersion' e import CurrentVersion from './CurrentVersion' que são as duas versões que estão em comparação.

Criei também a possibilidade de variar entre cada versão e também poder chamar diferentes tipos de classes do Hearthstone para realizar requições assíncronas diferentes.

Alternância entre versões:

<button onClick={() => setVersion('current')}>Current Version</button>
<button onClick={() => setVersion('experimental')}>Experimental Version</button>

Apresentação das versões de acordo com o que foi escolhido utilizando o setVersion

{version === 'current' && (<CurrentVersion cardClass={cardClass} />)}
{version === 'experimental' && (<ExperimentalVersion cardClass={cardClass} />)}

Agora crie os arquivos CurrentVersion.tsx e Experimental.tsx


CurrentVersion


CurrentVersion.tsx

import { useEffect, useState } from 'react'

interface CurrentVersionProps {
cardClass: string
}

function CurrentVersion({ cardClass }: CurrentVersionProps) {
const [data, setData] = useState([])

useEffect(() => {
const fetchCards = async (id: string) => {
const response = await fetch(`https://omgvamp-hearthstone-v1.p.rapidapi.com/cards/classes/${id}`, {
headers: {
'X-RapidAPI-Key': `${import.meta.env.VITE_RAPID_API_KEY}`,
'X-RapidAPI-Host': `${import.meta.env.VITE_RAPID_API_HOST}`
}
})

const dataResponse = await response.json()

// O filter foi adicionado para para filtrar cards que possuam o atributo `img`, do contrário vários cards ficarão "quebrados" durante a renderização da página.
// O slice é para poder retornar somente 20 cards para não ficar uma apresentação muito grande. Isso foi necessário uma vez que no momento da criação deste artigo a api não dispõe da possibilidade informar a quantidade de cartas para retornar.
setData((dataResponse.filter((card: any) => card.img)).slice(0, 19))
}

fetchCards(cardClass)
.catch(console.error)

}, [cardClass])

const renderClasses = (card: any, key: number) => {
return (
<img key={key} src={card.img} alt={card.name} height="256px" />
)
}

return (
<div>
<h2>Current Version</h2>
{data?.map(renderClasses)}
</div>
)
}

export default CurrentVersion

Para a Current Version faço uma chamada utilizando a api JavaScript fetch dentro do hook useEffect através do método fetchCards() informando o tipo da classe (este é selecionado no arquivo App.tsx, na escolha da classe).

Dessa forma, assim que a página é renderizada a chamada dos cards é realizada e logo após a conclusão e tratamento dos dados (filter e slice) os valores são "setados" através do hook setData.

Aqui faço uma pequena verificação com o Optional Chaining para apresentar os dados somente quando a variável data estiver preenchida

{data?.map(renderClasses)}

Essa abordagem exige dois hooks e sempre que realizo uma alternância entre as classes uma nova requisição vai ser realizada. Esse "monitoramento" é feito através do useEffect "ouvindo" o parâmetro [cardClass]

Além de sempre realizar uma nova requisição utilizar o useEffect pode acabar disparando N chamadas de acordo com a renderização da página. Inicialmente serão duas (isso pode ser contornado mas não realizei essa alteração neste projeto, de todo jeito é mais um passo manual).

Repare que na imagem abaixo sempre que altero entre cada classe é realizado uma nova requisição, independente se a chamada já havia sido realizada. Com o use essa abordagem pode ser diferente, vou explicar mais para frente.

Promise Current version
Promise Current Version

ExperimentalVersion


// Experimental.tsx

import { cache, use, Suspense } from "react"

interface CardsProps {
id: string
}

interface ExperimentalVersionProps {
cardClass: string
}

const fetchCards = cache(async (id: string) => {
const response = await fetch(`https://omgvamp-hearthstone-v1.p.rapidapi.com/cards/classes/${id}`, {
headers: {
'X-RapidAPI-Key': `${import.meta.env.VITE_RAPID_API_KEY}`,
'X-RapidAPI-Host': `${import.meta.env.VITE_RAPID_API_HOST}`
}
})

const dataResponse = await response.json()

// O filter foi adicionado para para filtrar cards que possuam o atributo `img`, do contrário vários cards ficarão "quebrados" durante a renderização da página.
// O slice é para poder retornar somente 20 cards para não ficar uma apresentação muito grande. Isso foi necessário uma vez que no momento da criação deste artigo a api não dispõe da possibilidade informar a quantidade de cartas para retornar.
return (dataResponse.filter((card: any) => card.img)).slice(0, 19)
})


function Cards({ id }: CardsProps) {
const cards = use(fetchCards(id))

const renderClasses = (card: any, key: number) => {
return (
<img key={key} src={card.img} alt={card.name} height="256px" />
)
}

return (
<div>
{cards?.map(renderClasses)}
</div>
)
}


function ExperimentalVersion({ cardClass }: ExperimentalVersionProps) {
return (
<div>
<h2>Experimental Version</h2>
<Suspense fallback={<h2>Loading…</h2>}>
<Cards id={cardClass} />
</Suspense>
</div>
)
}

export default ExperimentalVersion

A abordagem com o hook use começa a mudar; o primeiro ponto que descrevi neste artigo é o suporte a First Class Support, com isso o método para buscar os cards estão fora da função ExperimentalVersion, podendo assim realizar uma chamada e ler o conteúdo através uma promise com a api Suspense

Promise

const fetchCards = cache(async (id: string) => {
const response = await fetch(`https://omgvamp-hearthstone-v1.p.rapidapi.com/cards/classes/${id}`, {
// something
})

Suspense

<Suspense fallback={<h2>Loading…</h2>}>
<Cards id={cardClass} />
</Suspense>

Reparem que também adicionei um wrapper chamado cache na função fetchCards

cache(async (id: string) => {

Dessa maneira é realizado um "cacheamento" da requisição, com isso sempre que a mesma requisição é realizada o que estiver no cache vai ser retornando sem a necessidade de uma nova requisição na api, diferente do que acontece com a abordagem sem o wrapper de cache, conforme imagem abaixo:

Current Promise version
Promise Experimental Version

A utilização do cache não é obrigatória - por ora - mas é altamente recomendada pelo time de acordo com o que está na RFC. De todo jeito a utilização do cache pode sofrer várias abordagens, como necessidade de invalidação do mesmo ou mudanças de rota.

Mesmo assim a utilização do cache não substitui bibliotecas como react query do qual possui vários outros recursos e não apenas cacheamento.

Conclusão

Como pode ser observado a utilização do novo hook reduz drasticamente a lógica e quantidade de hooks, tornando o código mais simples. Além da possibilidade de combinação da api cache tornando as chamadas assíncronas muito mais eficientes. Apesar de não ter sido apresentado aqui é muito importante lembrar que essa aplicação também pode ser realizada com Server Components.

Alguns pontos ainda estão em abertos pela comunidade, como:

  • Como serão os tratamentos de erro?
  • Se adicionarão o controle de estado para pendente e rejeitado
  • Algumas pessoas são contras essa abordagem porque o ReactJS é uma biblioteca e não um framework.
  • A api de cache ainda será adicionada em uma RFC complementar porque ela precisa ser melhor abordada.

Por ora é isso, lembrando que essa NÃO é a versão final do hook use e que a discussão ainda está aberta, então alterações podem surgir ao longo do tempo. De todo jeito a comunidade está empenhanda em contribuir com essa possível melhoria e na minha opinião vai ser muito bem vinda porque ajuda bastante no desenvolvimento no dia-a-dia.

Espero que tenha gostado deste artigo e até o próximo ;)

note

Para visualizar o projeto completo acesse reactjs-use-hook

Referências