Criando uma Pokédex com Next.js - Parte 2
09 de novembro de 2022Esse post é a parte 2 de 3 de posts ensinando a criar um projeto com Next.js e hospedando ele na Vercel
Fala pessoal! Como vocês estão? Hoje vamos dar continuidade ao desenvolvimento da nossa Pokédex. Caso você não tenha acompanhado o post anterior, basta clicar no link acima para ver um pouco sobre o que é o Next.js, algumas de suas funcionalidades e também acompanhar o início do nosso projeto.
Conforme eu comentei no final do último post, hoje vamos usar a PokéAPI para mostrar os Pokémons de forma dinâmica e usar o roteamento para navegar para a tela de detalhes de cada Pokémon. Bora codar.
Configurando o consumo da PokéAPI
A PokéAPI é uma API pública com fins educacionais, portanto é uma opção para usarmos quando quisermos desenvolver algum projeto para ter em nosso portfólio. Você pode acessar a documentação dela clicando aqui e explorar as diversas opções que ela oferece. Aqui vamos focar no mais básico, já que é um projeto introdutório, mas você pode incrementar com coisas novas, como listagem dos Pokémons de acordo com os tipos ou até mostrar a evolução de cada um.
O primeiro passo será acessarmos o arquivo index.js dentro do diretório pages e removermos o array de Pokémons que colocamos no useState() inicialmente apenas para simular a funcionalidade de listagem dinâmica. O resultado vai ser o seguinte:
// ...
export default function Home() {
const [pokemons, setPokemons] = useState([]);
return (
// ...
);
}
O segundo passo será criar uma função que vai chamar a PokéAPI. Como mencionado antes, o intuito é fazer algo mais básico, então vamos passar uma quantidade fixa de 100 Pokémons para a API consultar. O resultado será o seguinte:
// ...
export default function Home() {
const [pokemons, setPokemons] = useState([]);
const getPokemons = () => {
const pokemonsPromise = Array(100)
.fill()
.map((_, index) =>
fetch(`https://pokeapi.co/api/v2/pokemon/${index + 1}`).then((res) =>
res.json()
)
);
Promise.all(pokemonsPromise).then((pokemons) => {
const formattedPokemons = pokemons.map(({ name, id, types, sprites }) => {
const elementTypes = types.map((type) => type.type.name);
return {
name,
id,
image: sprites.front_default,
elementTypes,
};
});
setPokemons(formattedPokemons);
});
};
return (
// ...
);
}
Explicando um pouco o que o código acima faz, ele cria um array de 100 posições e, para cada posição, faz uma chamada à API passando o índice do array + 1, já que o índice inicial de uma array é 0. Depois disso, ele agrupa todas as chamadas na API na variável pokemonsPromise, onde aguarda todas as chamadas serem finalizadas. Após todas as chamadas serem resolvidas, é feita uma tratativa para pegar apenas os dados necessários para essa primeira etapa. Em seguida, os dados dos 100 Pokémons são setados no useState().
Agora, para que tudo isso funcione, precisamos chamar essa função, e vamos fazer isso através do hook useEffect(). Ele basicamente funciona escutando algumas informações e quando essas mesmas são alteradas, ele executa algum comportamento, como no nosso caso, chamar a nossa função que acabamos de criar. O resultado vai ser o seguinte:
import { useEffect, useState } from "react";
// ...
export default function Home() {
const [pokemons, setPokemons] = useState([]);
const getPokemons = () => {
// ...
};
useEffect(() => {
getPokemons();
}, []);
return (
// ...
);
}
Note que, na primeira linha, importamos esse hook, e fazendo uso dele, percebemos que ele possui um array no final. É lá que vamos ter as informações que ele vai ficar observando, e caso mude, ele executa o comportamento novamente. No nosso caso, como não passamos nada, ele irá executar uma única vez quando a tela for carregada. Caso queira aprender um pouco mais sobre os hooks, como useState, useEffect, useCallback, entre outros, você pode clicar aqui e acessar a documentação oficial deles no site do React.
Pronto. Basta rodar o mesmo comando do post anterior (npm run dev
) e abrir o navegador no endereço http://localhost:3000
, que você deve ver a seguinte tela:
Se você observar bem, verá os 100 Pokémons que especificamos lá na função, sendo mostrados de forma dinâmica trazendo os dados através da API. Agora vamos criar a página de detalhes do Pokémon. Para isso, vamos ao terminal no projeto e digitar o seguinte comando:
mkdir pages/pokemon && touch pages/pokemon/\[pokemon\].js
Este comando irá criar um novo diretório dentro de pages
chamado pokemon
e dentro dele um arquivo com o nome [pokemon].js
. Como dissemos anteriormente, quando o nome de um arquivo está entre colchetes ([]
), significa que o roteamento do Next.js entende como um parâmetro dinâmico, e dessa forma podemos passar o nome dos Pokémons que queremos ver os detalhes.
Olhando um pouco o post passado, quando criamos a estrutura do arquivo index.js
, repare que dentro do map onde listamos os Pokémons, já criamos o link para a página de detalhes, veja abaixo como tinha ficado:
// ...
export default function Home() {
// ...
return (
// ...
{pokemons.map(({ name, id, image, elementTypes }) => (
<a
href={`pokemon/${name}`}
key={id}
className={`${styles.card} ${styles[elementTypes[0]]}`}
>
// ...
</a>
))}
// ...
);
}
Finalizado o desenvolvimento da tela inicial, vamos focar na nova página de detalhes. Abrindo o arquivo [pokemon].js
, vamos adicionar o seguinte código:
import Image from "next/image";
import { useRouter } from "next/router";
import { useEffect, useState } from "react";
import styles from "../../styles/Home.module.css";
export default function Pokemon() {
const [pokemonData, setPokemonData] = useState({});
const router = useRouter();
const { pokemon } = router.query;
const getPokemon = () => {
fetch(`https://pokeapi.co/api/v2/pokemon/${pokemon}`)
.then((res) => res.json())
.then((pokemonResponse) => {
const abilities = pokemonResponse.abilities.map(
(ability) => ability.ability.name
);
const game_indices = pokemonResponse.game_indices.map(
(game_index) => game_index.version.name
);
const moves = pokemonResponse.moves.map((move) => move.move.name);
const stats = pokemonResponse.stats.map((stat) => stat.stat.name);
const types = pokemonResponse.types.map((type) => type.type.name);
const data = {
base_experience: pokemonResponse.base_experience,
height: pokemonResponse.height,
image: pokemonResponse.sprites.front_default,
name: pokemonResponse.name,
weight: pokemonResponse.weight,
abilities: abilities.join(" | "),
game_indices: game_indices.join(" | "),
moves: moves.join(" | "),
stats: stats.join(" | "),
types: types.join(" | "),
};
setPokemonData(data);
});
};
useEffect(() => {
getPokemon();
}, [pokemon]);
return (
<div className={styles["container-details"]}>
<a href={`/`} className={styles["go-back"]}>
Voltar
</a>
<div className={styles.header}>
<Image
src={pokemonData.image}
className={styles["card-image"]}
width={175}
height={175}
alt={pokemonData.name}
/>
<h1 className={styles.name}>{pokemonData.name}</h1>
<div className={styles.details}>
<strong className={styles["details-title"]}>height: </strong>
<span className={styles["details-info"]}>{pokemonData.height}</span>
{" | "}
<strong className={styles["details-title"]}>base experience: </strong>
<span className={styles["details-info"]}>
{pokemonData.base_experience}
</span>
{" | "}
<strong className={styles["details-title"]}>weight: </strong>
<span className={styles["details-info"]}>{pokemonData.weight}</span>
</div>
<div className={styles.details}>
<strong className={styles["details-title"]}>types: </strong>
<span className={styles["details-info"]}>{pokemonData.types}</span>
</div>
<div className={styles.details}>
<strong className={styles["details-title"]}>abilities: </strong>
<span className={styles["details-info"]}>
{pokemonData.abilities}
</span>
</div>
<div className={styles.details}>
<strong className={styles["details-title"]}>stats: </strong>
<span className={styles["details-info"]}>{pokemonData.stats}</span>
</div>
<div className={styles.details}>
<strong className={styles["details-title"]}>game indices: </strong>
<span className={styles["details-info"]}>
{pokemonData.game_indices}
</span>
</div>
<div className={styles.details}>
<strong className={styles["details-title"]}>moves: </strong>
<span className={styles["details-info"]}>{pokemonData.moves}</span>
</div>
</div>
</div>
);
}
Explicando um pouco sobre o código acima, a estrutura básica é a mesma do arquivo index.js
. Nós exportamos uma função com o nome Pokemon
e a única coisa mais diferente é que importamos um hook chamado useRouter()
do pacote do Next.js, através do qual conseguimos pegar o parâmetro dinâmico que vem da rota. Também já adiantei e criei uma função que vai pegar os dados do Pokémon que queremos ver através da API e armazenei os dados em um useState()
. Se clicarmos no primeiro Pokémon da tela inicial, teremos algo parecido com essa rota http://localhost:3000/pokemon/bulbasaur
, e veremos o seguinte conteúdo na tela de detalhes:
Bom, vemos que a tela ainda está um pouco feia (ou bastante), então vamos estilizar um pouco essa tela. Para isso, vamos abrir o arquivo de estilização Home.module.css
e adicionar o seguinte código no final dele:
/* ... */
/* CSS da pagina de detalhes do Pokemon */
.container-details {
max-width: 36rem;
padding: 0 1rem;
margin: 3rem auto 6rem;
}
.go-back {
position: absolute;
font-size: 1.2rem;
top: 1.5rem;
left: 1.5rem;
text-decoration: none;
color: #666;
transition: 0.2s;
}
.go-back:hover {
color: #212121;
}
.header {
display: flex;
flex-direction: column;
align-items: center;
}
.name {
margin: 0.5rem 0;
}
.details {
margin: 0.5rem 0;
}
.details-title {
font-size: 1.2rem;
text-align: center;
}
.details-info {
font-size: 1.2rem;
text-align: center;
color: #666;
}
Pronto, basta recarregar a página novamente para ver o seguinte resultado:
Conclusão
Nesse segundo post, aprendemos um pouco sobre como trabalhar com dados dinâmicos vindos de chamadas em API e como funciona o sistema de roteamento e navegação entre páginas no Next.js. E claro, se você teve algum problema ou ficou com alguma dúvida, não esqueça de me enviar um email para que eu possa te ajudar. 🙃
No próximo post, vou explicar um pouco sobre a Vercel, plataforma de hospedagem de sites estáticos e aplicações que usam frameworks front-end, e como é fácil hospedar seu site lá (e claro, vamos hospedar esse projeto lá). Até mais 👋.