DEV Community

loading...
Cover image for Create pokedex with react hooks

Create pokedex with react hooks

pes profile image Pascual Hdez Updated on ・15 min read

Hello, I wrote this guide with the purpose to show how to develop a simple app using react, and configuring webpack and babel by hand. I hope that could be helpful if you are starting to work with this technologies, or if you have plans to use them in the near future.

Before start

Before start coding we need to add some software to our computer:

  • We'll use npm to manage the app dependencies, so we need to install nodejs.
  • A code editor, you can choose your favorite, but I recommend to use VS Code.

And that's it, now we will are able to develop the app.

Let's Start

The first thing that we need to do is initialize the app, to do this we need to use a terminal and move to the folder where we'll create the app (you can do this with the command cd [directory]), once we are on that directory we need to type the following command in the console:

npm init

This command will open a prompt to ask you some initial configurations for the app in order to generate the package.json file:

Alt Text

After fill the information and type yes to save, we should be able to open VS code or the code editor that we choose, once we open the folder we need to open the embedded terminal on the editor and continue installing the dependencies from there.

Install the dependencies

First we need to install the app dependencies, in this case we gonna use react, react-dom, and axios, so we'll to type in the terminal the following command:

npm i react react-dom axios

Then we should proceed to install the development dependencies, this dependencies are only to be able to have a development environment to test the app, add libraries, check errors, and run the app in the local host environment.

For this app, we will use webpack and babel to generate the bundles, so we'll run this command on the console to install them as dev dependencies:

npm i @babel/core@^7.12.3 babel-loader@^8.1.0 babel-preset-react-app@^7.0.2 css-loader@^5.0.0 html-webpack-plugin@^4.5.0 style-loader@^2.0.0 webpack@^4.44.2 webpack-cli@^3.3.12 webpack-dev-server@^3.11.0 --save-dev

* In this case I specify the library version to avoid problems when we will start configuring webpack and babel

Once we install all the dependencies the package.json file should look in the following way:

{
  "name": "pokeapp",
  "version": "1.0.0",
  "description": "demo app",
  "main": "index.js",
  "scripts": {
    "start": "webpack-dev-server"
  },
  "author": "PHD",
  "license": "ISC",
  "dependencies": {
    "axios": "^0.20.0",
    "react": "^17.0.1",
    "react-dom": "^17.0.1"
  },
  "devDependencies": {
    "@babel/core": "^7.12.3",
    "babel-loader": "^8.1.0",
    "babel-preset-react-app": "^7.0.2",
    "css-loader": "^5.0.0",
    "html-webpack-plugin": "^4.5.0",
    "style-loader": "^2.0.0",
    "webpack": "^4.44.2",
    "webpack-cli": "^3.3.12",
    "webpack-dev-server": "^3.11.0"
  },
  "babel": {
    "presets": [
      "babel-preset-react-app"
    ]
  }
}
Enter fullscreen mode Exit fullscreen mode

* We added some babel configuration to transpile the react app correctly.

"babel": {
    "presets": [
      "babel-preset-react-app"
    ]
  }
Enter fullscreen mode Exit fullscreen mode

* Also we add in the scripts section the command a script start the app when we'll finish the first configurations.

"scripts": {
    "start": "webpack-dev-server"
  },
Enter fullscreen mode Exit fullscreen mode

Configure webpack

Now we have our dependencies ready, the following step is to setup webpack, to do this we need to add a webpack.config.js file in the root folder, this step is necessary only to have a better control of what happens when we build the app.

Webpack expects a list of options to generate the bundles based on that configuration, so we need to export that options in the following way:

const webpack = require("webpack");
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");

process.env.NODE_ENV = "development";

module.exports = {
    ... // webpack options goes here
};
Enter fullscreen mode Exit fullscreen mode

To correctly setup webpack we need to set the following options on the exports section:

  1. mode. This setting is to allow to webpack's built-in optimizations that correspond to each environment (development or production).

    mode: "development"
    
  2. target. In this option we could select if the deployment should be on the server or on the browser, we can do more configurations like have multiple targets but are off the scope of this guide.

    target: "web"
    
  3. devtool. With this option we can control if we generate a source map and which type of source map we would use, the source map allow us to debug easily our compiled code on the browser.

    devtool: "cheap-module-source-map"
    
  4. entry. This setting allow us to define the entry point of the app.

    entry: "./src/index"
    
  5. output. This key indicate to webpack how and where it should output the bundles and assets.

    output: {
    path: path.resolve(__dirname, "build"),
    publicPath: "/",
    filename: "pokebundle.js",
    }
    
  6. devServer. In this guide we would use devServer to develop the app, this option allow us to configure how this server should run in the local host.

    devServer: {
    open: true,
    stats: "minimal",
    overlay: true,
    historyApiFallback: true,
    disableHostCheck: true,
    headers: { "Access-Control-Allow-Origin": "*" },
    https: false,
    }
    
  7. plugins. This key is to configure the webpack plugins, those plugins help us to do extra actions when we bundle the app, in this case we gonna use HtmlWebpackPlugin to serve some html files with the app bundle.

    plugins: [
    new HtmlWebpackPlugin({
      template: "src/index.html",
    }),
    ]
    
  8. module. This option determine how webpack would process the different modules of the app.

    module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        use: ["babel-loader"],
      },
      {
        test: /(\.css)$/,
        use: ["style-loader", "css-loader"],
      },
    ],
    }
    

The complete webpack.config.js file should looks like the following one:

const webpack = require("webpack");
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");

process.env.NODE_ENV = "development";

module.exports = {
  mode: "development",
  target: "web",
  devtool: "cheap-module-source-map",
  entry: "./src/index",
  output: {
    path: path.resolve(__dirname, "build"),
    publicPath: "/",
    filename: "pokebundle.js",
  },
  devServer: {
    open: true,
    stats: "minimal",
    overlay: true,
    historyApiFallback: true,
    disableHostCheck: true,
    headers: { "Access-Control-Allow-Origin": "*" },
    https: false,
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: "src/index.html",
    }),
  ],
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        use: ["babel-loader"],
      },
      {
        test: /(\.css)$/,
        use: ["style-loader", "css-loader"],
      },
    ],
  },
};
Enter fullscreen mode Exit fullscreen mode

Run the app

We gonna use a simple folder structure just a src main folder and inside other two folders api and components where we will put all our files:

Alt Text

Now is time to start coding

The first file that we need to add is the app main component, to do that go to the components folder and create an App.js file, then put inside that file the following code:

import React from "react";
function App() {
  return (
    <div className="container">
      Pokedex goes here
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

This react hook returns a simple component that renders a div with some text inside.

To render this component properly we need to add the entry point to the app, to do that go to src folder and create a index.js file, then type the following code:

import React from "react";
import { render } from "react-dom";
import App from "./components/App";

document.addEventListener("DOMContentLoaded", () => {
  render(<App />, document.getElementById("app"));
});
Enter fullscreen mode Exit fullscreen mode

This code is to render the app in an html page, the render function looks for an element with id "app" after the DOM content is loaded, and then try to render our component there.

But we don't have any html page yet, so we need to add an html page in the src folder to use it as a template for the app, please create an index.html file and put the following content there:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>Pokedex</title>
    <link
      rel="stylesheet"
      href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css"
      integrity="sha384-TX8t27EcRE3e/ihU7zmQxVncDAy5uIKz4rEkgIXeMed4M0jlfIDPvg6uqKI2xXr2"
      crossorigin="anonymous"
    />
    <script
      src="https://code.jquery.com/jquery-3.5.1.slim.min.js"
      integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj"
      crossorigin="anonymous"
    ></script>
    <script
      src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/js/bootstrap.bundle.min.js"
      integrity="sha384-ho+j7jyWK8fNQe+A12Hb8AhRq26LrZ/JpcUGGOn+Y7RsweNrtN/tE3MoK7ZeZDyx"
      crossorigin="anonymous"
    ></script>
    <script
      src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js"
      integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN"
      crossorigin="anonymous"
    ></script>
  </head>

  <body>
    <div id="app"></div>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

Notice how we added some style sheets to the html head, we did that to use Bootstrap in the app.

Now we are ready to run the app, just go to the console and type npm start this command should bundle the app and open the default browser with the running application.

Get Pokemon information

To get the Pokemon information we gonna use the Pokemon API this APIs provide some endpoints that we could use to get all the information that we need for the app, but first we need to create some files to connect the app to any REST API.

Add the following files to the api folder ApiService.js and ApiUtils.js, then put the following code in the ApiService.js file:

import axios from "axios"; // this library is to fetch data from REST APIs
import { handleError, handleResponse } from "./ApiUtils";

const httpRequest = (method, url, request, headers) => {
  // return a promise
  return axios({
    method,
    url,
    data: request,
    headers,
  })
    .then((res) => {
      const result = handleResponse(res);
      return Promise.resolve(result);
    })
    .catch((err) => {
      return Promise.reject(handleError(err));
    });
};

const get = (url, request, headers) => {
  let queryString = "";
  if (request && Object.keys(request).length > 0) {
    queryString += "?";
    let len = Object.keys(request).length,
      cnt = 0;

    // transform the request object in a query string
    for (let key in request) {
      cnt++;
      queryString += `${key}=${request[key].toString()}`;
      if (len > cnt) queryString += "&";
    }
  }
  return httpRequest("get", `${url}${queryString}`, request, headers);
};

const deleteRequest = (url, request, headers) => {
  return httpRequest("delete", url, request, headers);
};

const post = (url, request, headers) => {
  return httpRequest("post", url, request, headers);
};

const put = (url, request, headers) => {
  return httpRequest("put", url, request, headers);
};

const patch = (url, request, headers) => {
  return httpRequest("patch", url, request, headers);
};

const Api = {
  get,
  delete: deleteRequest,
  post,
  put,
  patch,
};

export default Api;
Enter fullscreen mode Exit fullscreen mode

In this file we use axios to do the REST requests, we handle the responses with other two functions handleResponse and handleError those methods are imported from the ApiUtils.js file, also we add some logic to the get method to have a consistent way to do any REST request, at the end of the file we export all the methods inside an Api object.

For the ApiUtils.js file we need to write the following code, to handle appropriately the server responses:

export function handleResponse(response) {
    if (
      response.status === 200 ||
      response.status === 202 ||
      response.statusText === "OK" ||
      response.statusText === "Created"
    )
      return response.data;
    if (response.status === 400) {
      // So, a server-side validation error occurred.
      // Server side validation returns a string error message, so parse as text instead of json.
      const error = response.statusText();
      throw new Error(error);
    }
    throw new Error("Network response was not ok.");
  }

  // In a real app, would likely call an error logging service.
  export function handleError(error) {
    console.error("API call failed. " + error);
    throw error;
  }
Enter fullscreen mode Exit fullscreen mode

Now is time to connect the app to the Pokemon API, we need to create a PokemonService.js file inside of api folder, in this file we would add all the methods to get the Pokemon information.

First we need to import the api dependencies into the service:

import ApiService from "./ApiService";
Enter fullscreen mode Exit fullscreen mode

Then we could define the three async methods that we'll use:

  1. getKantoPokemon. This method will get a list with all kanto Pokemon, with this list we'll be able to get more data for all the pokemons.

    export const getKantoPokemon = async () => {
    try {
    let response = await ApiService.get(`https://pokeapi.co/api/v2/pokemon`, {
      limit: 151,
    });
    return response.results;
    } catch (err) {
    throw err;
    }
    };
    
  2. getPokemonData. This method is to get the Pokemon details, this method require an URL to get the Pokemon information.

    export const getPokemonData = async (url) => {
    try {
    let response = await ApiService.get(url);
    return response;
    } catch (err) {
    throw err;
    }
    };
    
  3. getPokemonKantoData. This method use the first two methods, the first one to get all kanto Pokemon and the second one to get the details of all the pokemons on the response of the first call.

    export const getPokemonKantoData = async () => {
    try {
    //get pokemon list
    let pokemons = await getKantoPokemon();
    
    //get promises to obtain data for all pokemon in the list
    let pokemonPromises = pokemons.map((p) => getPokemonData(p.url));
    
    //return all the pokemon data
    return await Promise.all(pokemonPromises);
    } catch (err) {
    throw err;
    }
    };
    

The complete code of this file is the following:

import ApiService from "./ApiService";

export const getKantoPokemon = async () => {
  try {
    let response = await ApiService.get(`https://pokeapi.co/api/v2/pokemon`, {
      limit: 151,
    });
    return response.results;
  } catch (err) {
    throw err;
  }
};

export const getPokemonData = async (url) => {
  try {
    let response = await ApiService.get(url);
    return response;
  } catch (err) {
    throw err;
  }
};

export const getPokemonKantoData = async () => {
  try {
    //get pokemon list
    let pokemons = await getKantoPokemon();

    //get promises to obtain data for all pokemon in the list
    let pokemonPromises = pokemons.map((p) => getPokemonData(p.url));

    //return all the pokemon data
    return await Promise.all(pokemonPromises);
  } catch (err) {
    throw err;
  }
};
Enter fullscreen mode Exit fullscreen mode

Create the Pokedex components

We'll use three components, we need to create the home folder inside of components and then proceed to create the following files:

  1. HomeContainer.js this component will act as our container.

  2. PokemonList.js this component will display all the list of Pokemons.

  3. PokemonDetail.js in this component we will display the Pokemon details once the user click on one element of the list.

Also we need to add some css styles, so to handle those styles in one file we need to create the pokemon.css file in the src folder.

PokemonList component

In this functional component we need to receive the Pokemon list, and which is the selected Pokemon as props, the first prop is to display all Pokemons in a friendly way, and the second one is to be able to highlight the selected Pokemon.

First we need to do the imports that we'll use:

import React from "react";
import "../../pokemon.css";
Enter fullscreen mode Exit fullscreen mode

Then we need to create the functional component:

function PokemonList({ pokemons, selectPokemon }) {
    ... // draw Pokemon function goes here
    ... // return goes here
};
Enter fullscreen mode Exit fullscreen mode

If the pokemons array prop have records we'll return an <li> item for each object in the array, in this tag we can properly render the items to display them in a friendly way:

  const drawPokemon = () => {
    return pokemons.map((p, id) => (
      <li
        key={id}
        onClick={() => selectPokemon(p.id)}
        className={
          p.selected
            ? "list-group-item d-flex pokemon-item-list selected"
            : "list-group-item d-flex pokemon-item-list"
        }
      >
        <img className="col-3" src={p.sprites.front_default} />
        <p className="col-4 pokemon-text-list">N.º {p.id}</p>
        <p className="col-5 pokemon-text-list">{p.name}</p>
      </li>
    ));
  };
Enter fullscreen mode Exit fullscreen mode

In the return of the component, we need to check if the pokemons prop length is greater than 0, because we'll get the data from the server, and when the component is rendered at the screen this prop will not have data:

return <ul className="list-group">{pokemons.length > 0 && drawPokemon()}</ul>;
Enter fullscreen mode Exit fullscreen mode

And finally don't forget to export the component to be able to use it:

export default PokemonList;
Enter fullscreen mode Exit fullscreen mode

The complete file component should look like the following:

import React from "react";
import "../../pokemon.css";

function PokemonList({ pokemons, selectPokemon }) {
  const drawPokemon = () => {
    return pokemons.map((p, id) => (
      <li
        key={id}
        onClick={() => selectPokemon(p.id)}
        className={
          p.selected
            ? "list-group-item d-flex pokemon-item-list selected" // the selected class is to highlight the Pokemon selected
            : "list-group-item d-flex pokemon-item-list"
        }
      >
        <img className="col-3" src={p.sprites.front_default} />
        <p className="col-4 pokemon-text-list">N.º {p.id}</p>
        <p className="col-5 pokemon-text-list">{p.name}</p>
      </li>
    ));
  };

  return <ul className="list-group">{pokemons.length > 0 && drawPokemon()}</ul>;
}

export default PokemonList;
Enter fullscreen mode Exit fullscreen mode

PokemonDetail component

This functional component will render the details of the selected Pokemon, the name, a picture, the Pokemon types, etc.

First we need to import the libraries that we'll use:

import React from "react";
Enter fullscreen mode Exit fullscreen mode

Then we need to create the component body:

function PokemonDetail({ pokemon }) {
    ... // getTypeStyleFunction goes here
    ... // return goes here
}
Enter fullscreen mode Exit fullscreen mode

In this component we use the getTypeStyle function, this function is to used to get some css styles that rely on the Pokemon type:

const getTypeStyle = (type) => {
    let backgroundColor = "";
    switch (type) {
      case "grass":
        backgroundColor = "#9bcc50";
        break;
      case "poison":
        backgroundColor = "#b97fc9";
        break;
      case "fire":
        backgroundColor = "#fd7d24";
        break;
      case "flying":
        backgroundColor = "#3dc7ef";
        break;
      case "water":
        backgroundColor = "#4592c4";
        break;
      case "bug":
        backgroundColor = "#729f3f";
        break;
      case "normal":
        backgroundColor = "#a4acaf";
        break;
      case "electric":
        backgroundColor = "#eed535";
        break;
      case "ground":
        backgroundColor = "#ab9842";
        break;
      case "fairy":
        backgroundColor = "#fdb9e9";
        break;
      case "fighting":
        backgroundColor = "#d56723";
        break;
      case "psychic":
        backgroundColor = "#f366b9";
        break;
      case "rock":
        backgroundColor = "#a38c21";
        break;
      case "steel":
        backgroundColor = "#9eb7b8";
        break;
      case "ghost":
        backgroundColor = "#7b62a3";
        break;
      case "ice":
        backgroundColor = "#51c4e7";
      case "dragon":
        backgroundColor = "#f16e57";

      default:
        backgroundColor = "#000";
        break;
    }
    return { backgroundColor, color: "#FFF", margin: "5px" };
  };
Enter fullscreen mode Exit fullscreen mode

Then in the return we render some html to display the Pokemon selected in a friendly way:

return (
    <div className="pokemon-image-container">
      <h1 className="text-center">
        N.º {pokemon.id} {pokemon.name}
      </h1>
      <img
        src={`https://pokeres.bastionbot.org/images/pokemon/${pokemon.id}.png`}
        className="img-fluid pokemon-image-detail d-block mx-auto"
      />
      <div className="pokemon-box-details">
        <ul className="list-group list-group-horizontal justify-content-center">
          {pokemon.types.length > 0 &&
            pokemon.types.map((t, idx) => (
              <li
                key={idx}
                className="list-group-item d-flex pokemon-list-details"
                style={getTypeStyle(t.type.name)}
              >
                {t.type.name}
              </li>
            ))}
        </ul>
      </div>
    </div>
  );
Enter fullscreen mode Exit fullscreen mode

Finally don't forget to export the component:

export default PokemonDetail;
Enter fullscreen mode Exit fullscreen mode

The complete file component should look like the following:

import React from "react";

function PokemonDetail({ pokemon }) {
  const getTypeStyle = (type) => {
    let backgroundColor = "";
    switch (type) {
      case "grass":
        backgroundColor = "#9bcc50";
        break;
      case "poison":
        backgroundColor = "#b97fc9";
        break;
      case "fire":
        backgroundColor = "#fd7d24";
        break;
      case "flying":
        backgroundColor = "#3dc7ef";
        break;
      case "water":
        backgroundColor = "#4592c4";
        break;
      case "bug":
        backgroundColor = "#729f3f";
        break;
      case "normal":
        backgroundColor = "#a4acaf";
        break;
      case "electric":
        backgroundColor = "#eed535";
        break;
      case "ground":
        backgroundColor = "#ab9842";
        break;
      case "fairy":
        backgroundColor = "#fdb9e9";
        break;
      case "fighting":
        backgroundColor = "#d56723";
        break;
      case "psychic":
        backgroundColor = "#f366b9";
        break;
      case "rock":
        backgroundColor = "#a38c21";
        break;
      case "steel":
        backgroundColor = "#9eb7b8";
        break;
      case "ghost":
        backgroundColor = "#7b62a3";
        break;
      case "ice":
        backgroundColor = "#51c4e7";
      case "dragon":
        backgroundColor = "#f16e57";

      default:
        backgroundColor = "#000";
        break;
    }
    return { backgroundColor, color: "#FFF", margin: "5px" };
  };

  return (
    <div className="pokemon-image-container">
      <h1 className="text-center">
        N.º {pokemon.id} {pokemon.name}
      </h1>
      <img
        src={`https://pokeres.bastionbot.org/images/pokemon/${pokemon.id}.png`}
        className="img-fluid pokemon-image-detail d-block mx-auto"
      />
      <div className="pokemon-box-details">
        <ul className="list-group list-group-horizontal justify-content-center">
          {pokemon.types.length > 0 &&
            pokemon.types.map((t, idx) => (
              <li
                key={idx}
                className="list-group-item d-flex pokemon-list-details"
                style={getTypeStyle(t.type.name)}
              >
                {t.type.name}
              </li>
            ))}
        </ul>
      </div>
    </div>
  );
}

export default PokemonDetail;
Enter fullscreen mode Exit fullscreen mode

HomeContainer component

This functional component act as a container, so in this component we we'll import the other two components, we gonna get access to the APIs, and also we'll use some hooks like useEffect to get the Pokemon list when the screen loads, useState to handle the state of the component, and send that state as props to the child components.

First we need to import the libraries and components that we'll use:

import React, { useEffect, useState } from "react";
import PokemonList from "./PokemonList";
import PokemonDetail from "./PokemonDetail";
import { getPokemonKantoData } from "../../api/PokemonService";
Enter fullscreen mode Exit fullscreen mode

Then we need to create the component body:

function HomeContainer() {
    ...// state declarations goes here
    ...// use effect goes here
    ...// functions goes here
    ...// return goes here
}
Enter fullscreen mode Exit fullscreen mode

The states that we need to use will be the following

  • pokeList. To handle the complete list of pokemons.
  • filteredPokeList. To handle the list of pokemons filtered.
  • filter. To set which pokemons we will filter.
  • pokemonSelected. To handle the Pokemon selected.
  const [pokeList, setPokeList] = useState([]);
  const [filteredPokeList, setFilteredPokeList] = useState([]);
  const [filter, setFilter] = useState("");
  const [pokemonSelected, setPokemonSelected] = useState(null);
Enter fullscreen mode Exit fullscreen mode

Then we need to get the Pokemon list when the app loads, to do this action we need to use the useEffect hook, to call the API that gets the information:

  useEffect(async () => {
    try {
      let pokemons = await getPokemonKantoData();
      setFilteredPokeList(pokemons);
      setPokeList(pokemons);
    } catch (err) {
      alert("an error occurs");
      console.error(err);
    }
  }, []);
Enter fullscreen mode Exit fullscreen mode

To have the filter functionality we can use a function to set the state filteredPokeList based on the received value:

  const filterPokemon = (value) => {
    setFilter(value); // set the filter value
    setFilteredPokeList(
      pokeList.filter((p) => p.name.toLowerCase().includes(value.toLowerCase()))
    ); // set the pokemons that match with the value
  };
Enter fullscreen mode Exit fullscreen mode

To highlight the selected Pokemon, and also to display the Pokemon details, we need to create a function that sets the pokemonSelected state:

  const handleSelect = (pokemonId) => {
    setPokemonSelected(pokeList.filter((p) => p.id === pokemonId)[0]); // set the selected Pokemon to display the details
    setFilteredPokeList(
      filteredPokeList.map((p) =>
        p.id === pokemonId
          ? { ...p, selected: true }
          : { ...p, selected: false }
      )
    ); // filter the list of pokemons to display
  };
Enter fullscreen mode Exit fullscreen mode

Finally, we need to return the container structure to display the app:

return (
    <div className="row pokemon-app-container">
      <div className="col-6">
        {pokemonSelected && <PokemonDetail pokemon={pokemonSelected} />}
      </div>
      <div className="col-6 pokemon-list-container">
        <div style={{ height: "10%" }}>
          <div className="form-group">
            <label>Search</label>
            <input
              type="text"
              className="form-control"
              placeholder="Type to search a pokemon..."
              value={filter}
              onChange={(event) => {
                let { value } = event.target;
                filterPokemon(value);
              }}
            />
          </div>
        </div>
        <div style={{ height: "90%", overflowY: "auto" }}>
          <PokemonList
            pokemons={filteredPokeList}
            selectPokemon={handleSelect}
          />
        </div>
      </div>
    </div>
  );
Enter fullscreen mode Exit fullscreen mode

Finally export the component to be able to use it:

export default HomeContainer;
Enter fullscreen mode Exit fullscreen mode

The complete code of this component should look like the following:

import React, { useEffect, useState } from "react";
import PokemonList from "./PokemonList";
import PokemonDetail from "./PokemonDetail";
import { getPokemonKantoData } from "../../api/PokemonService";

function HomeContainer() {
  useEffect(async () => {
    try {
      let pokemons = await getPokemonKantoData();
      console.log(pokemons);
      setFilteredPokeList(pokemons);
      setPokeList(pokemons);
    } catch (err) {
      alert("an error occurs");
      console.error(err);
    }
  }, []);

  const [pokeList, setPokeList] = useState([]);
  const [filteredPokeList, setFilteredPokeList] = useState([]);
  const [pokemonSelected, setPokemonSelected] = useState(null);
  const [filter, setFilter] = useState("");

  const handleSelect = (pokemonId) => {
    setPokemonSelected(pokeList.filter((p) => p.id === pokemonId)[0]);
    setFilteredPokeList(
      filteredPokeList.map((p) =>
        p.id === pokemonId
          ? { ...p, selected: true }
          : { ...p, selected: false }
      )
    );
  };

  const filterPokemon = (value) => {
    setFilter(value);
    setFilteredPokeList(
      pokeList.filter((p) => p.name.toLowerCase().includes(value.toLowerCase()))
    );
  };
  return (
    <div className="row pokemon-app-container">
      <div className="col-6">
        {pokemonSelected && <PokemonDetail pokemon={pokemonSelected} />}
      </div>
      <div className="col-6 pokemon-list-container">
        <div style={{ height: "10%" }}>
          <div className="form-group">
            <label>Search</label>
            <input
              type="text"
              className="form-control"
              placeholder="Type to search a pokemon..."
              value={filter}
              onChange={(event) => {
                let { value } = event.target;
                filterPokemon(value);
              }}
            />
          </div>
        </div>
        <div style={{ height: "90%", overflowY: "auto" }}>
          <PokemonList
            pokemons={filteredPokeList}
            selectPokemon={handleSelect}
          />
        </div>
      </div>
    </div>
  );
}

export default HomeContainer;
Enter fullscreen mode Exit fullscreen mode

The Pokemon css stylesheet

I don't want to go deep in the css because I think is out of the scope of this guide, so I only add the style sheet here:

.pokemon-item-list {
  border-radius: 40px !important;
  margin-top: 10px;
  margin-bottom: 10px;
  border-width: 0px;
}

.pokemon-item-list.selected {
  background-color: #e3350d;
  color: white;
  border-width: 1px;
}

.pokemon-item-list:hover {
  border-width: 1px;
  background-color: #E2E2E2;
  color: white;
}

.pokemon-text-list {
  font-size: 24px;
  margin-top: 20px;
}

.pokemon-app-container {
  height: 100vh;
}

.pokemon-list-container {
  height: 100%;
  overflow-y: auto;
}

.pokemon-image-container {
  margin-top: 4rem;
  border: 1px solid #F2F2F2;
  background-color: #F2F2F2;
  border-radius: 20px;
  padding: 10px;
}

.pokemon-image-detail {
  height: 400px;
}

.pokemon-list-details {
  margin-top: 20px;
  border-width: 0px;
}

.pokemon-box-details {
  margin-top: 10px;
}
Enter fullscreen mode Exit fullscreen mode

Ending the app

Finally we need to update our App.js file this to load the components that we created:

import React from "react";
import Home from "./Home/HomeContainer"; // import the container component

// return the Home component
function App() {
  return (
    <div className="container">
      <Home /> 
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

With this the app should be completed, but we can improve many parts of them, like add proptypes, use redux, refactor the code in some parts, optimize the bundles, etc.

You can get the complete code in the following repository PokeApp

If you are here, I want to say you thank you so much for read this guide, and if you have any comment I really appreciate your feedback.

Discussion

pic
Editor guide
Collapse
tjjone98 profile image
Vu
export const COLOR: any = {
  fire: '#FD7D24',
  water: '#4592C4',
  grass: '#9BCC50',
  electric: '#EED535',
  dragon: '#4A446A',
  dark: '#707070',
  psychic: '#F366B9',
  ice: '#51C4E7',
  flying: '#649BD4',
  bug: '#729F3F',
  fairy: '#FDB9E9',
  ground: '#704326',
  normal: '#A4ACAF',
  rock: '#A38C21',
  fighting: '#D56723',
  poison: '#B97FC9',
  ghost: '#7B62A3',
  steel: '#84B7B8',
};

export const getColor = (type: any): string => {
  return COLOR[type] || COLOR.typeNormal;
}; 
Enter fullscreen mode Exit fullscreen mode

Use object literals instead of switch case and you have sort code.
This is example in my pokedex app.

Collapse
robertnayael profile image
Robert Gulewicz

Btw, you can easily make it more type-safe:

const COLOR = {
  fire: '#FD7D24',
  water: '#4592C4',
  grass: '#9BCC50',
  electric: '#EED535',
  dragon: '#4A446A',
  dark: '#707070',
  psychic: '#F366B9',
  ice: '#51C4E7',
  flying: '#649BD4',
  bug: '#729F3F',
  fairy: '#FDB9E9',
  ground: '#704326',
  normal: '#A4ACAF',
  rock: '#A38C21',
  fighting: '#D56723',
  poison: '#B97FC9',
  ghost: '#7B62A3',
  steel: '#84B7B8',
};

export const getColor = (type: keyof typeof COLOR): string => {
  return COLOR[type] || COLOR.normal;
}; 
Enter fullscreen mode Exit fullscreen mode
Collapse
pes profile image
Pascual Hdez Author

Thanks for your comment, I agree with you, the object literals looks better on js code, but my intention with this tutorial is to guide developers that have less experience in javascript, so a switch case sentence might looks more natural for them, I think that I can do a second part of this tutorial and do some code refactoring in some places, and also apply redux and other technologies, thanks for the feedbak :)

Collapse
tjjone98 profile image
Vu

No problem pro. Pokedex is my favorite app when I start begin new frontend Framework. Thanks for your post :) .