Interacting with the Pokemon API

Documenting my learnings with APIs starting with the Pokemon API


Project Title: Pokemon API

TO-DO: Add a section on how to interact with the API using Server-Side API routes. Implement search functionality to search for a specific pokemon. Format and spell check the document.

Introduction

Project Overview: Interact and Display information with the Pokemon API Objective: Get familiar with Fetch API and how to interact with APIs.

Background

Motivation: I have always been interested in APIs and how they work. I have always wanted to learn how to interact with them and how to display the information in a user-friendly way. I have decided to start with the Pokemon API as it is a simple API to start with. Prerequisites: Basic understanding of Next.js and React

Planning and Set Up

Tools: Next.js, React, Pokemon API Environment: Local

Implementation

Steps: Set up next.js project, create a new component, fetch data from the Pokemon API, display the data in the component.

npx create-next-app pokemon-api-app

Depending on the use case, there are many different ways to interact with an API. We will start with the most basic way to interact with an API using the fetch API. In this case we will creating a library folder and a file called pokemon.ts. This file will contain the logic to fetch the data from the Pokemon API. In our pokemon component file, we will be implementating a click event to fetch the pokemon details from the specific pokemon.

export const getPokemonList = async () => {
	const response = await fetch("https://pokeapi.co/api/v2/pokemon");
	if (!response.ok) {
		throw new Error("Network response was not ok");
	}
	return response.json();
};
 
export const getPokemonDetails = async (name: string) => {
	const response = await fetch(`https://pokeapi.co/api/v2/pokemon/${name}`);
	if (!response.ok) {
		throw new Error("Network response was not ok");
	}
	return response.json();
};

Breaking down the code:

Each function is responsible for fetching a different type of data from the API. getPokemonList fetches a list of 20 Pokemon per the API documentation. We will fetch all the pokemon later on in the project. Marking the function as async allows us to use the await keyword to wait for the response. The endpoints we are using is pokemon and pokemon/name to get the list of pokemon and the details of a specific pokemon.

Displaying the data

To display the data were are going to create a new component called PokemonStats.tsx.

Here is my thought process when trying to display the data, what data will we need and what data will we be displaying. From getPokemonList we will need the name of the pokemon and the url to fetch the details of the pokemon. From getPokemonDetails we will need the name, height, weight, and a sprite of the pokemon.

Creating the types for the data we will be fetching:

interface Pokemon {
	name: string;
	url: string;
}
 
interface PokemonDetails {
	name: string;
	sprites: {
		front_default: string;
	};
	stats: {
		stat: {
			name: string;
		};
		base_stat: number;
	}[];
}

We will need 4 states to in our component.

  1. pokemonList - to store the list of pokemon
const [pokemonList, setPokemonList] = useState<Pokemon[]>([]);
  1. pokemonDetails - to store the details of the pokemon
const [pokemonDetails, setPokemonDetails] = useState<PokemonDetails | null>(
	null
);
  1. selectedPokemon - to store the name of the selected pokemon
const [selectedPokemon, setSelectedPokemon] = useState<string>("");
  1. loading - to store the loading state of the component
const [loading, setLoading] = useState<boolean>(false);

Next we will create a useEffect hook to fetch the list of pokemon when the component mounts.

useEffect(() => {
	setIsLoading(true);
	getPokemonList()
		.then((data) => {
			setPokemonList(data.results);
			console.log("Pokemon list:", data.results);
		})
		.catch((error) => {
			console.error("Failed to fetch Pokemon list:", error);
		})
		.finally(() => {
			setIsLoading(false);
		});
}, []);

It is also good practice to handle the loading state of the component, this will help the user know that the data is being fetched. To achieve this we are setting the loading state to true before fetching the data and setting it to false after the data has been fetched. Another good practice is to handle the error state of the component. This will help the user know that there was an error fetching the data.

Next we will create a function to fetch the details of the pokemon when the user clicks on the pokemon name.

useEffect(() => {
	if (selectedPokemon) {
		setIsLoading(true);
		getPokemonDetails(selectedPokemon)
			.then((data) => {
				setPokemonDetails(data);
				console.log("Pokemon details:", data);
			})
			.catch((error) => {
				console.error("Failed to fetch Pokemon details:", error);
			})
			.finally(() => {
				setIsLoading(false);
			});
	}
}, [selectedPokemon]);

Notice how we are using the selectedPokemon as the dependency in the useEffect hook. This will ensure that the useEffect hook is called when the selectedPokemon changes.

 
...
return (
	<div>
		<h1>Pokemon Stats</h1>
			<select
				value={selectedPokemon}
				onChange={(e) => setSelectedPokemon(e.target.value)}>
				<option value="">Select a Pokemon</option>
				{pokemonList.map((pokemon) => (
					<option key={pokemon.name} value={pokemon.name}>
						{pokemon.name}
					</option>
				))}
			</select>
			{isLoading ? (
				<div>Loading...</div>
			) : (
				pokemonDetails && (
					<div>
						<h2>{pokemonDetails.name}</h2>
						<img
							src={pokemonDetails.sprites.front_default}
							alt={pokemonDetails.name}
						/>
						<ul>
							{pokemonDetails.stats.map((stat) => (
								<li key={stat.stat.name}>
									{stat.stat.name}: {stat.base_stat}
								</li>
							))}
						</ul>
					</div>
				)
			)}
	</div>
)

Here displaying a select dropdown to select the pokemon and displaying the details of the pokemon when the user selects a pokemon. We are creating a select element that maps over the pokemonList and creates an option element for each pokemon. When the user selects a pokemon, we are setting the selectedPokemon state to the name of the selected pokemon. We are displaying the loading state when the data is being fetched and displaying the pokemon details when the data has been fetched.

The full code can be found here. I also implemented shadcnUI to style the component. This was the first way I learned to interact with an API. Next we will be looking at how to interact with an API using Server-Side API routes.

Switching to Server-Side API routes

Server-Side API routes are a great way to interact with an API. They allow you to fetch data from an API and return it to the client. They are also a great way to interact with an API that requires authentication. I made the mistake of trying to interact with an API that required authentication with out learning the basics first.

Server-Side API routes are recommended for more complex or larger projects. Also they allow for catch-all routes and dynamic routes.