I recently leveled up my React skills by building a Pokédex! This was such a fun project that I wanted to share the process with you all. The app allows users to search for Pokémon by name or ID, fetching detailed information from the PokéAPI, including their type, abilities, and game appearances.
You can check out the live version of the Pokédex app here.
Prerequisites
Before getting started, make sure you have the following prerequisites:
- Basic knowledge of React and JavaScript
- Node.js and npm installed on your machine
This project is ideal for developing your React skills, learning to use an API, and experimenting with Material-UI components.
Project Setup and Installation
Initialize the Project
First, create a new React application using Create React App. This will create a basic skeleton for our React application:
npx create-react-app pokemon-finder
cd pokemon-finder
This command creates a new folder containing all the necessary files for a React application. Once installed, navigate into the folder to get started.
Install Dependencies
Next, install the necessary libraries, including axios for making HTTP requests and @mui/material for UI components:
npm install axios @mui/material @emotion/react @emotion/styled framer-motion
- Axios: This library is essential for interacting with the PokéAPI.
- @mui/material: Provides pre-built UI components that help us quickly create a modern and functional interface.
- Framer Motion: To add smooth animations to your components, enhancing the user experience.
Adding Custom Fonts
To give the application a unique look, we can use custom fonts. Here's how to add fonts to your project:
- Create a folder named fonts inside the src/assets directory.
- Place your font files inside this folder.
- Create a CSS file named fonts.css inside the src/assets directory and import your fonts like this:
@font-face {
font-family: 'GeneralSans';
src: url('./fonts/GeneralSans-Regular.woff2') format('woff2'),
url('./fonts/GeneralSans-Regular.woff') format('woff');
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'PokemonPixel';
src: url('./fonts/PokemonPixel.ttf') format('truetype');
font-weight: normal;
font-style: normal;
}
- Import the fonts.css file in your App.js file:
import './assets/fonts/fonts.css';
This allows you to use your custom fonts throughout the application.
Creating the Components
To structure our application, we will create several React components. This will make code management easier and allow us to reuse elements.
Create a Components Folder
Before starting to build components, create a folder named components in the src directory. We will place all our component files in this folder.
Header
The Header component is a simple navigation bar at the top of the application. Here is the code for the Header:
import React, { Component } from 'react';
import AppBar from '@mui/material/AppBar';
import Toolbar from '@mui/material/Toolbar';
import Typography from '@mui/material/Typography';
import Box from '@mui/material/Box';
import Icon from '../assets/images/logo.png';
import '../assets/fonts/fonts.css';
class Header extends Component {
render() {
return (
<AppBar position="static" sx={{ backgroundColor: '#ef233c' }}>
<Toolbar>
<Box sx={{ display: 'flex', alignItems: 'center', flexGrow: 1 }}>
<img src={Icon} alt="logo" style={{ marginRight: 10, width: 40, height: 40 }} />
<Typography variant="h6" component="div" sx={{fontFamily: 'GeneralSans', fontSize: '1.5rem'}}>
POKEMON FINDER
</Typography>
</Box>
</Toolbar>
</AppBar>
);
}
}
export default Header;
This component uses Material-UI to create a clean and responsive app bar. It includes a logo and the name of the application.
PokemonCard
The PokemonCard component is used to display detailed information about a Pokémon, including its name, type, abilities, and generation:
import React from 'react';
import { Card, CardContent, Typography, CardMedia, Divider, Stack, CircularProgress } from '@mui/material';
import '../assets/fonts/fonts.css';
import { motion, AnimatePresence } from 'framer-motion';
const variants = {
initial: { opacity: 0, y: 20 },
animate: { opacity: 1, y: 0, transition: { type: 'spring', stiffness: 50, damping: 10 } },
exit: { opacity: 0, y: -20, transition: { duration: 0.3 } },
};
const cardStyles = {
display: 'flex',
flexDirection: { xs: 'column', sm: 'row' },
margin: '20px auto',
width: '90%',
minWidth: 300,
maxWidth: 600,
overflow: 'hidden',
backgroundColor: '#f6f6f6',
boxShadow: '0 0 10px 0 rgba(0,0,0,0.2)',
transition: 'transform 0.3s, box-shadow 0.3s',
'&:hover': {
transform: 'scale(1.02)',
boxShadow: '0 0 20px 0 rgba(0,0,0,0.3)',
},
};
const contentStyles = {
flex: '1',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
padding: 2,
fontFamily: 'Arial, PokemonPixel',
};
const PokemonCard = ({ pokemon, loading, isShiny }) => {
if (loading) {
return (
<AnimatePresence>
<motion.div
key="loading"
variants={variants}
initial="initial"
animate="animate"
exit="exit"
>
<Card sx={cardStyles}>
<CardContent sx={contentStyles}>
<CircularProgress />
</CardContent>
</Card>
</motion.div>
</AnimatePresence>
);
}
if (!pokemon) return null;
const types = pokemon.types.map(typeInfo => typeInfo.type.name).join(', ');
const abilities = pokemon.abilities.map(abilityInfo => abilityInfo.ability.name).join(', ');
const { generation, description, id } = pokemon;
const imageUrl = isShiny ? pokemon.sprites.front_shiny : pokemon.sprites.front_default;
return (
<AnimatePresence>
<motion.div
key={id}
variants={variants}
initial="initial"
animate="animate"
exit="exit"
>
<Card sx={cardStyles}>
<CardMedia
component="img"
sx={{
width: { xs: '100%', sm: 170 },
height: { xs: 170, sm: 'auto' },
objectFit: 'contain',
}}
image={imageUrl}
alt={pokemon.name}
/>
<CardContent sx={contentStyles}>
<Stack spacing={2} alignItems="left">
<Typography gutterBottom variant="h5" component="div" sx={{ fontFamily: 'PokemonPixel', textAlign: 'left', fontSize: '2rem' }}>
{pokemon.name} (#{id})
</Typography>
<Divider variant="middle" sx={{ bgcolor: '#ef233c', width: '100%' }} />
<Typography variant="subtitle1" component="p" sx={{ fontFamily: 'Roboto', fontSize: '1rem' }}>
<b>Description:</b> {description}
</Typography>
<Typography variant="subtitle1" component="p" sx={{ fontFamily: 'Roboto', fontSize: '1rem' }}>
<b>Type:</b> <span style={{ color: '#4A90E2' }}>{types}</span>
</Typography>
<Typography variant="subtitle1" component="p" sx={{ fontFamily: 'Roboto', fontSize: '1rem' }}>
<b>Generation:</b> {generation.replace('generation-', '').toUpperCase()}
</Typography>
<Typography variant="subtitle1" component="p" sx={{ fontFamily: 'Roboto', fontSize: '1rem' }}>
<b>Abilities:</b> {abilities}
</Typography>
</Stack>
</CardContent>
</Card>
</motion.div>
</AnimatePresence>
);
};
export default PokemonCard;
SearchBar
The SearchBar component provides the input field and search button for the user to enter the Pokémon name or ID:
import React from 'react';
import TextField from '@mui/material/TextField';
import Button from '@mui/material/Button';
const SearchBar = ({ onSearch }) => {
return (
<form onSubmit={onSearch} style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '20px', margin: '20px' }}>
<TextField
name="pokemonName"
label="Enter a Pokémon name or ID"
variant="outlined"
fullWidth
style={{ maxWidth: '500px' }}
/>
<Button type="submit" variant="contained" color="primary">
Search
</Button>
</form>
);
};
export default SearchBar;
This component allows the user to initiate a search by clicking a button or pressing the Enter key.
Main application file
The App component is the heart of the application, managing state and handling API requests. Here is the full code for the App component:
import React, { useState } from 'react';
import axios from 'axios';
import { CircularProgress, FormControlLabel, Switch, Box } from '@mui/material';
import SearchBar from './components/SearchBar';
import PokemonCard from './components/PokemonCard';
import Header from './components/Header';
import './assets/fonts/fonts.css';
import './App.css';
function App() {
const [pokemonData, setPokemonData] = useState(null);
const [loading, setLoading] = useState(false);
const [isShiny, setIsShiny] = useState(false);
const cleanDescription = (description) => description.replace(/\f/g, ' ');
const fetchPokemonData = async (pokemonNameOrId) => {
setPokemonData(null);
setLoading(true);
try {
const sanitizedInput = pokemonNameOrId.toLowerCase().replace(/^0+/, '');
const baseResponse = await axios.get(`https://pokeapi.co/api/v2/pokemon/${sanitizedInput}`);
const speciesResponse = await axios.get(baseResponse.data.species.url);
const generation = speciesResponse.data.generation.name;
const flavorTextEntries = speciesResponse.data.flavor_text_entries.filter(entry => entry.language.name === 'en');
let description = flavorTextEntries.length > 0 ? flavorTextEntries[0].flavor_text : 'No description available.';
description = cleanDescription(description);
setPokemonData({
...baseResponse.data,
generation,
description
});
} catch (error) {
window.alert('Pokémon not found. Please try a different name or ID.');
} finally {
setLoading(false);
}
};
return (
<div className="App">
<Header />
<SearchBar onSearch={(e) => {
e.preventDefault();
const pokemonName = e.target.elements.pokemonName.value.trim();
if (pokemonName) fetchPokemonData(pokemonName);
}} />
<Box sx={{ display: 'flex', justifyContent: 'center', mt: 1 }}>
<FormControlLabel
control={
<Switch checked={isShiny} onChange={(e) => setIsShiny(e.target.checked)} color="primary" />
}
label="Show Shiny"
/>
</Box>
{loading && (
<div style={{ display: 'flex', justifyContent: 'center', marginTop: '20%' }}>
<CircularProgress />
</div>
)}
{pokemonData && <PokemonCard pokemon={pokemonData} isShiny={isShiny} />}
</div>
);
}
export default App;
This component also manages the "shiny" state to allow the user to view the shiny version of the Pokémon, which adds a fun touch to the application.
Conclusion
Congratulations, you have successfully built a Pokédex using React and the PokéAPI! You can now search for any Pokémon by name or ID and view detailed information about them.
Feel free to explore and add more features to your app, such as displaying Pokémon stats or comparing multiple Pokémon. For more details and to contribute to this project, check out the Pokemon Finder repository on GitHub.
I hope this guide was helpful and that you had fun while improving your React skills! If you have any questions or suggestions, feel free to leave them in the comments.
Top comments (1)
thanks for posting this, got me interested in and helped me create my first React app. I've learnt a tonne from it since!