Building a Pokedex with React
What if Pokémon were real? As a kid growing up in the 90s, we've all wondered about this; catching Pokémon, battling in gyms, and using that cool Pokédex to give us the information we need. Obviously, there are no Pokémon in real life (Pokémon Go is the closest thing we can get to "real") but that doesn't mean we can't have a cool Pokédex, right?
So let's build our own Pokédex using React!
(Read related article: What if Programming Languages were Pokémon?)
React.js Library
Facebook has come up with the React library which has shaken the front-end world with its shrewdness. Its rendering capabilities are quite fantastic and the simplistic JSX syntax makes things even better. Let's check out how we can build a really cool Pokédex with React and a few npm tools.
Before we go any further, please make sure that you have Node.js and npm installed on your machine. If you're a beginner, here's a guide to help you be familiar with the React environment.
Let's get started!
1. Installing dependencies
Create a folder for your project and open the terminal in the root of the folder. To initialize your project, type:
npm init
You might need to press the Enter key a few times to set up your package.json
Now run:
npm install --save-dev babel-cli babel-loader babel-plugin-add-module-exports babel-plugin-transform-runtime babel-preset-es2015 babel-preset-react babel-preset-stage-2 isomorphic-fetch react react-dom react-hot-loader webpack webpack-dev-server
This will install React into your project and also a few Babel plugins and presets that will come handy in transpiling (converting) our ES6 + React code into ES5 JavaScript so that browsers are able to run it.
Remember, not all browsers are able to run ES6 yet.
2. Setting up Pokémon monster sprites
- Create a folder named
public
in the root of your project. - Download this zip file from here.
- Extract this zip file into your public folder.
- Make sure that your folder structure so far looks like this.
3. Webpack & Babel configuration
Webpack is a tool that allows us to bundle all of our JavaScript files into ES5 using Babel plugins. So, all we do is run our code in React and ES6 and Webpack helps us convert it instantly into ES5 standards. Let's add a webpack.config.js
file to our project.
var path = require('path');
var webpack = require('webpack');
var assetsPath = path.join(__dirname, 'src');
module.exports = {
entry : {
bundle : ['webpack-dev-server/client?http://0.0.0.0:8080',
'webpack/hot/only-dev-server',
path.resolve(assetsPath,'index.js')],
},
output: {
chunkFilename: '[name].js',
filename: '[name].js', //
path: path.join(assetsPath ,"dist/js/"),
publicPath: 'http://localhost:8080/assets/'
},
module: {
loaders: [
{
//tell webpack to use jsx-loader for all *.jsx files
test: /.jsx?$/,
loaders: ['react-hot-loader/webpack','babel'],
include: [path.resolve(assetsPath)]
}
]
},
resolve: {
extensions: ['', '.js', '.jsx']
},
devtool : '#source-map',
plugins: [
new webpack.DefinePlugin({
'process.env': {
'NODE_ENV': '"development"'
}
}),
new webpack.HotModuleReplacementPlugin()
]
};
3.1 Understanding the Webpack configuration
- entry – This is where Webpack looks for as a starting point for the bundle.
index.js
withinsrc
folder of our project is where the Pokedex React app resides in. We will create this shortly. - output – The
publicPath
key is crucial in this configuration. This means that our JavaScript will be served athttp://localhost:8080/assets/bundle.js
. - loaders – This tells Webpack how it should render files with a particular extension. In our case, all files with .js or .jsx extension are treated as files that need to be converted to ES5 using Babel and React loaders.
You can also read more about Webpack in this getting started tutorial.
Create a .babelrc file in the root folder of the project and have this as its content.
{
"presets" : ["es2015", "stage-2", "react"],
"plugins" : ["add-module-exports","transform-runtime"]
}
This file tells Babel that when Webpack loads the babel-loader
, it should transpile it to ES6 and React using these presets and plugins. You can read more about them here.
4. Index.html and style.css
Create an index.html
file in the root folder of the project, which should look like this:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Pokedex | React</title>
<link rel="stylesheet" href="style.css" media="screen" title="no title">
</head>
<body>
<div id="app">
</div>
<script src="http://localhost:8080/assets/bundle.js" charset="utf-8"></script>
</body>
</html>
Let's add a few styles to make our app look nicer. Create a style.css
file in the root folder of the project with this CSS.
HTML, body{
font-family : sans-serif;
background: #e2e1e0;
text-align : center;
}
.pokeapp{
padding : 16px;
}
.pokemon--species--list{
display : -webkit-box;
display : -ms-flexbox;
display : flex;
-ms-flex-wrap : wrap;
flex-wrap : wrap;
padding : 16px 0;
}
.pokemon--species{
-ms-flex-preferred-size : 25%;
flex-basis : 25%;
padding : 8px;
box-sizing: border-box;
}
.pokemon--species--container{
padding : 8px;
background : white;
cursor : pointer;
box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24);
}
5. Scaffolding a React component
Create a folder named src
, if you haven't already done that. Create a file index.js
within the src folder.
Lets us first create a React Component named PokeApp
. This component will be the root component of this project.
Also, in our index.html
, we created a div with id app remember? Let's mount our PokeApp
onto the div#app
.
//This is the root component
import {render} from 'react-dom';
import React, {Component} from 'react';
class PokeApp extends Component{
render(){
return <div className="pokeapp">
<h1> The Kanto PokeDex! </h1>
</div>;
}
}
render(<PokeApp/>,document.getElementById('app'))
Right now, our app doesn't do anything but let's go ahead and run it with Webpack and see what we have so far. All we have to do is to run:
node_modules/webpack-dev-server/bin/webpack-dev-server.js
in the terminal
in the root folder of the project. Also, open up http://localhost:8080. You should see this on your screen.
6. Fetching Pokedex from the PokeApi API
Paul Hallett has been very kind to build a killer of an API to help developers build apps on Pokémon. You can read more about it here.
Let's enhance our index.js
and add a couple of components. We will create a component named Pokemon
to display an individual Pokémon and we will create another component named PokemonList
to fetch Pokédex data and display a list of Pokemon
components.
5.1 Pokémon Component
Pokemon
component should be a rather simple component which accepts two props, id
as well as pokemon
. id
is the ID of the Pokémon on the Pokédex. For eg: Charmander
has an id
- 4
. And Charmander's pokemon
object would look like this.
{
url: "http://pokeapi.co/api/v2/pokemon/4/",
name: "charmander"
}
So, let's go ahead and create it.
//The Pokemon component will show an individual Pokemon monster
// It shows an image of the Pokemon and
// shows the name of it as well.
class Pokemon extends Component{
render(){
const {pokemon,id} = this.props;
return <div className="pokemon--species">
<div className="pokemon--species--container">
<div className="pokemon--species--sprite">
<img src={`/public/sprites/${id}.png`} />
</div>
<div className="pokemon--species--name"> {pokemon.name} </div>
</div>
</div>;
}
}
5.2 PokemonList Component
The PokemonList component is responsible for fetching the Pokemon data from the PokeApi. It should look something like this.
//The PokemonList component shows nothing when it mounts for the first time.
//But right before it mounts on to the DOM, it makes an
//API call to fetch the first 151 Pokemon from the API and
//then displays them using the Pokemon Component
class PokemonList extends Component{
constructor(props){
super(props);
this.state = {
species : [],
fetched : false,
loading : false,
};
}
componentWillMount(){
this.setState({
loading : true
});
fetch('http://pokeapi.co/api/v2/pokemon?limit=151').then(res=>res.json())
.then(response=>{
this.setState({
species : response.results,
loading : true,
fetched : true
});
});
}
render(){
const {fetched, loading, species} = this.state;
let content ;
if(fetched){
content = <div className="pokemon--species--list">{species.map((pokemon,index)=><Pokemon key={pokemon.name} id={index+1} pokemon={pokemon}/>)}</div>;
}else if(loading && !fetched){
content = <p> Loading ...</p>;
}
else{
content = <div/>;
}
return <div>
{content}
</div>;
}
}
It starts off with a state
wherein the species
array is empty and the fetched
boolean is set to false
. This tells the component that when it is in this state, it does not have much to show to the user.
When the component is just about to mount, however, the loading
boolean is set to true
which allows us to show a nice Loading ...
text on our screen while the isomorphic-fetch
library fetches the Pokedex data from the API, converts it to JSON and loads it into the species
array. At this point loading
is set to false
and fetched
is set to true
. This means that the app now has all the Pokémon loaded in its state.
Now, all we need to do is iterate over all the Pokemon in the species array and for each of the Pokemon render the Pokemon
component with id
(since the index is 0-based, id = index + 1
) and pokemon
props set accordingly.
And, that's it!
Now that we have our components ready. Let's go ahead and add them to our PokeApp Component. So by now, our index.js
file should look like this.
import {render} from 'react-dom';
import React, {Component} from 'react';
import fetch from 'isomorphic-fetch';
//The Pokemon component will show an individual Pokemon monster
// It shows an image of the Pokemon and
// shows the name of it as well.
class Pokemon extends Component{
render(){
const {pokemon,id} = this.props;
return <div className="pokemon--species">
<div className="pokemon--species--container">
<div className="pokemon--species--sprite">
<img src={`/public/sprites/${id}.png`} />
</div>
<div className="pokemon--species--name"> {pokemon.name} </div>
</div>
</div>;
}
}
//The PokemonList component shows nothing when it mounts for the first time.
//But right before it mounts on to the DOM, it makes an
//API call to fetch the first 151 Pokemon from the API and
//then displays them using the Pokemon Component
class PokemonList extends Component{
constructor(props){
super(props);
this.state = {
species : [],
fetched : false,
loading : false,
};
}
componentWillMount(){
this.setState({
loading : true
});
fetch('http://pokeapi.co/api/v2/pokemon?limit=151').then(res=>res.json())
.then(response=>{
this.setState({
species : response.results,
loading : true,
fetched : true
});
});
}
render(){
const {fetched, loading, species} = this.state;
let content ;
if(fetched){
content = <div className="pokemon--species--list">{species.map((pokemon,index)=><Pokemon key={pokemon.name} id={index+1} pokemon={pokemon}/>)}</div>;
}else if(loading && !fetched){
content = <p> Loading ...</p>;
}
else{
content = <div/>;
}
return <div>
{content}
</div>;
}
}
//This is the root component
class PokeApp extends Component{
render(){
return <div className="pokeapp">
<h1> The Kanto PokeDex! </h1>
<PokemonList/>
</div>;
}
}
render(<PokeApp/>,document.getElementById('app'))
Let's have a look at our app now. BAM!!
I hope you had a fun time building this app with me. Feel free to ping me for questions and yes, you can download the source here. Just run npm install
and npm start
to start the app.
Hello! I’m really impressed with the React code you’ve generated for building a Pokédex, and I noticed you mentioned the https://pokemonsrom.com/ website. Could you please share more details about how your code interacts with pokemonsrom.com and what kind of data or features users can expect to access from that website through your Pokédex application?
Hello, thanks for this. When I download the code, run nom install the npm start I get this error: “Tell the author that this fails on your system:
npm ERR! node_modules/webpack-dev-server/bin/webpack-dev-server.js”
I’d like to see this app in action to better understand it.
Well, it could be possible that webpack-dev-server has a different path now. So you will have to move the command into your npm scripts and instead of using the absolute path (node_modules/…/webpack-dev-server.js), just put “start” :“webpack-dev-server” inside your package.json. Let me know if that worked for you.
I’m having problems with the api, the app won’t show any of the pokemons. it just stays stuck in Loading…
Yes. Pokeapi has some performance issues. They are working on it.