richdoherty.dev

Making Calls to an API (With Pokémon)

Richard Doherty | January 20, 2023

An image of my pokedex app.

View Project

View Code

Background

A few months ago I wanted to create a project that would help me get the hang of back-end development. I heard that PokeApi is a good starting point for learning about API's.

What I Used to Make This App

I built this app using Next.js, TypeScript, Tailwind, and PokeApi.

I also made use of a few tutorials, in particular this article from CompileTab, and this video by Dom the dev.

First Steps

First I created a new Next.js project with the command:

npx create-next-app@latest --typescript pokedex
npx create-next-app@latest --typescript pokedex

Then I installed Tailwind and started setting up the API.

Setting up the API

getStaticProps

Next.js has a function getStaticProps which can be used to fetch some data from an API and pass it to the page as props before the page is rendered.

I added this code to the end of index.tsx:

export async function getStaticProps() {
    return {
        props: {},
    };
}
export async function getStaticProps() {
    return {
        props: {},
    };
}

Fetching the Data

Once this function is in place, the call to PokeApi can be made inside:

export async function getStaticProps() {
    const res = await fetch("https://pokeapi.co/api/v2/pokemon?limit=151");
    const pokemon = await res.json();
 
    return {
        props: {
            pokemon,
        },
    };
}
export async function getStaticProps() {
    const res = await fetch("https://pokeapi.co/api/v2/pokemon?limit=151");
    const pokemon = await res.json();
 
    return {
        props: {
            pokemon,
        },
    };
}

The data is fetched from the API, turned into json, and returned as props.

I like to console.log() the data to make sure it is being retrieved correctly. Here is what that data looks like.

The array of pokemon data fetched from PokeApi.

Displaying the data

Now that we know the data is being fetched properly, we want to display that data on the page.

Creating the Component

To do this, I decided to create a new component called "PokemonCard" which will contain a single Pokemon's information.

PokemonCard.jsx
import Link from "next/link";
import Image from "next/image";
 
export default function PokemonCard(props) {
    return (
        <div key={props.index}>
            <Link href={"pokemon/" + props.entry.name}>
                <div>
                    <h2>{props.entry.name}</h2>
                    <p>#{props.index + 1}</p>
                </div>
            </Link>
        </div>
    );
}
PokemonCard.jsx
import Link from "next/link";
import Image from "next/image";
 
export default function PokemonCard(props) {
    return (
        <div key={props.index}>
            <Link href={"pokemon/" + props.entry.name}>
                <div>
                    <h2>{props.entry.name}</h2>
                    <p>#{props.index + 1}</p>
                </div>
            </Link>
        </div>
    );
}

I imported the PokemonCard component and used props to get the text to show up for each Pokemon. The number has to be incremented by 1 to display the correct number since the array starts at index 0.

Rendering the Cards

Next, to get all of the PokemonCards to show up on the page, I used a map function in index.tsx to render a PokemonCard component for each Pokemon.

index.tsx
export default function Home({ pokemon }: any) {
    const { results } = pokemon;
    return (
        <div>
            <Head>
                <title>Pokédex</title>
                <meta
                    name="description"
                    content="Pokedex app created by Richard Doherty"
                />
                <link rel="icon" href="/favicon.png" />
            </Head>
            <main>
                <h1>Pokédex</h1>
                <div>
                    {results.map((entry: Pokemon, index: number) => (
                        <PokemonCard entry={entry} index={index} key={index} />
                    ))}
                </div>
            </main>
        </div>
    );
}
index.tsx
export default function Home({ pokemon }: any) {
    const { results } = pokemon;
    return (
        <div>
            <Head>
                <title>Pokédex</title>
                <meta
                    name="description"
                    content="Pokedex app created by Richard Doherty"
                />
                <link rel="icon" href="/favicon.png" />
            </Head>
            <main>
                <h1>Pokédex</h1>
                <div>
                    {results.map((entry: Pokemon, index: number) => (
                        <PokemonCard entry={entry} index={index} key={index} />
                    ))}
                </div>
            </main>
        </div>
    );
}

Adding an Image

One more thing to do before adding styling is adding an image. First we want to go back to the PokemonCard.jsx file. We can use Next.js' Image component to display the image.

<Image
    id={imgId}
    src={`https://img.pokemondb.net/sprites/black-white/normal/${props.entry.name}.png`}
    alt={props.entry.name}
    width="96"
    height="96"
/>
<Image
    id={imgId}
    src={`https://img.pokemondb.net/sprites/black-white/normal/${props.entry.name}.png`}
    alt={props.entry.name}
    width="96"
    height="96"
/>

However, you may receive the following error:

Error: Invalid src prop (https://img.pokemondb.net/sprites/black-white/normal/bulbasaur.png) on next/image, hostname "img.pokemondb.net" is not configured under images in your next.config.js See more info: https://nextjs.org/docs/messages/next-image-unconfigured-host

To fix this you must go into your next.config.js and add this code:

next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
    reactStrictMode: true,
    images: {
        remotePatterns: [
            {
                protocol: "https",
                hostname: "raw.githubusercontent.com",
            },
            {
                protocol: "https",
                hostname: "img.pokemondb.net",
            },
        ],
    },
};
 
module.exports = nextConfig;
next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
    reactStrictMode: true,
    images: {
        remotePatterns: [
            {
                protocol: "https",
                hostname: "raw.githubusercontent.com",
            },
            {
                protocol: "https",
                hostname: "img.pokemondb.net",
            },
        ],
    },
};
 
module.exports = nextConfig;

Your app should now look like this:

Pokedex App with no styling.

Adding Background Color

This is the part that took the longest to figure out. First, I created a new type.css file with classes for each Pokemon type. I found the colors on Bulbapedia.

types.css
.normal {
    @apply bg-[#A8A878];
}
.fire {
    @apply bg-[#F08030];
}
.water {
    @apply bg-[#6890F0];
}
.electric {
    @apply bg-[#F8D030];
}
.grass {
    @apply bg-[#78C850];
}
.ice {
    @apply bg-[#98D8D8];
}
.fighting {
    @apply bg-[#C03028];
}
.poison {
    @apply bg-[#A040A0];
}
.ground {
    @apply bg-[#E0C068];
}
.flying {
    @apply bg-[#A890F0];
}
.psychic {
    @apply bg-[#F85888];
}
.bug {
    @apply bg-[#A8B820];
}
.rock {
    @apply bg-[#B8A038];
}
.ghost {
    @apply bg-[#705898];
}
.dragon {
    @apply bg-[#7038F8];
}
.dark {
    @apply bg-[#705848];
}
.steel {
    @apply bg-[#B8B8D0];
}
.fairy {
    @apply bg-[#EE99AC];
}
types.css
.normal {
    @apply bg-[#A8A878];
}
.fire {
    @apply bg-[#F08030];
}
.water {
    @apply bg-[#6890F0];
}
.electric {
    @apply bg-[#F8D030];
}
.grass {
    @apply bg-[#78C850];
}
.ice {
    @apply bg-[#98D8D8];
}
.fighting {
    @apply bg-[#C03028];
}
.poison {
    @apply bg-[#A040A0];
}
.ground {
    @apply bg-[#E0C068];
}
.flying {
    @apply bg-[#A890F0];
}
.psychic {
    @apply bg-[#F85888];
}
.bug {
    @apply bg-[#A8B820];
}
.rock {
    @apply bg-[#B8A038];
}
.ghost {
    @apply bg-[#705898];
}
.dragon {
    @apply bg-[#7038F8];
}
.dark {
    @apply bg-[#705848];
}
.steel {
    @apply bg-[#B8B8D0];
}
.fairy {
    @apply bg-[#EE99AC];
}

Now for the tricky part. You may have noticed earlier that the call to PokeApi returns an array of objects containing a name and a url. To get a Pokemon's attributes, I wanted the type here, the object's url must be fetched.

I created a new function "fetchPokemon" to fetch the data within the PokemonCard component and made use of useEffect to call the new function.

PokemonCard.jsx
async function fetchPokemon(props) {
    const res = await fetch(props.entry.url);
    const pokemon = await res.json();
    const type = Promise.resolve(pokemon.types[0].type.name);
 
    try {
        const value = await type;
        document.querySelector("#" + imgId).classList.add(value);
        return value;
    } catch (err) {
        console.log(err);
    }
}
 
useEffect(() => {
    fetchPokemon(props);
});
PokemonCard.jsx
async function fetchPokemon(props) {
    const res = await fetch(props.entry.url);
    const pokemon = await res.json();
    const type = Promise.resolve(pokemon.types[0].type.name);
 
    try {
        const value = await type;
        document.querySelector("#" + imgId).classList.add(value);
        return value;
    } catch (err) {
        console.log(err);
    }
}
 
useEffect(() => {
    fetchPokemon(props);
});

This function gets the type from the API and appends the string to the image's classList. The background of the image then becomes whatever color corresponds to the type.

Creating a Page For Each Pokemon

Next, I created a page that contains some more info on each Pokemon. Each PokemonCard component will have a link with an href of {"pokemon/" + props.entry.name}. The urls for each page will follow this format.

I created a folder inside the pages directory called pokemon which corresponds to the "pokemon/" portion of the href.

Inside the pokemon folder I created a folder called [id] and a file index.tsx inside of it. That file looks like this:

pokemon/[id]/index.tsx
import Image from "next/image";
 
export default function pokemon({ pokemon }: any) {
    return (
        <main>
            <h1>{pokemon.name}</h1>
 
            <div>
                <Image
                    src={
                        pokemon.sprites.other["official-artwork"].front_default
                    }
                    alt={pokemon.name}
                    width="200"
                    height="200"
                />
                <div>
                    <p>Height: {pokemon.height / 10} m</p>
                    <p>Weight: {pokemon.weight / 10} kg</p>
                    {pokemon.types.map((slot: any, index: number) => (
                        <p key={slot.type.name}>
                            <span className={`${slot.type.name}`}>
                                {slot.type.name}
                            </span>
                        </p>
                    ))}
                </div>
            </div>
        </main>
    );
}
 
export async function getServerSideProps(context: any) {
    const id = context.params.id;
    const res = await fetch(`https://pokeapi.co/api/v2/pokemon/${id}`);
    const pokemon = await res.json();
 
    return {
        props: { pokemon },
    };
}
pokemon/[id]/index.tsx
import Image from "next/image";
 
export default function pokemon({ pokemon }: any) {
    return (
        <main>
            <h1>{pokemon.name}</h1>
 
            <div>
                <Image
                    src={
                        pokemon.sprites.other["official-artwork"].front_default
                    }
                    alt={pokemon.name}
                    width="200"
                    height="200"
                />
                <div>
                    <p>Height: {pokemon.height / 10} m</p>
                    <p>Weight: {pokemon.weight / 10} kg</p>
                    {pokemon.types.map((slot: any, index: number) => (
                        <p key={slot.type.name}>
                            <span className={`${slot.type.name}`}>
                                {slot.type.name}
                            </span>
                        </p>
                    ))}
                </div>
            </div>
        </main>
    );
}
 
export async function getServerSideProps(context: any) {
    const id = context.params.id;
    const res = await fetch(`https://pokeapi.co/api/v2/pokemon/${id}`);
    const pokemon = await res.json();
 
    return {
        props: { pokemon },
    };
}

In this file, I used getServerSideProps to retrieve the data from the database. The parameter context that is passed into the funciton contains params which contains whatever is in place of [id] in the url. For example, if you clicked on Bulbasaur, the full url would be "pokedex-app.com/pokemon/bulbasaur" and params would look like { id: "bulbasaur" }. Now you will be able to access the data for the pokemon you want.

With some styling, the page should now look like this:

The page containing more information about Bulbasaur

Conclusion

That's about it! You can find the code on my GitHub if you want to take a look at the code or see how I styled mine. Or you can style yours from scratch. I hope this post was informative and thank you for reading!

GitHubLinkedIn

richdoherty7@gmail.com

This site was built with Next.jsMDXTailwind,  and Vercel.