skip to Main Content

Hello I am making an website that shows pokemons from "https://pokeapi.co/api/v2/pokemon"
and I am displaying them with .map(). every one of those has image title and link I want to make link to go to the /{pokemon.name} and display it there but I don’t want to create file for every pokemon I want to have one file for every of them and display things based on which pokemon’s link was clicked and I want every to have also diffrent url like: pikachu -> http://example.com/pikachu, bulbasaur -> http://example.com/bulbasaur but I want to have one file for every one of them.

App.jsx:

import React from "react";
import Pokemons from "./Pokemons";
import {
  BrowserRouter,
  createBrowserRouter,
  RouterProvider,
} from "react-router-dom";
import Pokemon from "./Pokemon";

function App() {
  const router = createBrowserRouter([
    {
      path: "/",
      element: <Pokemons />,
    },
    {
      path: "/pikachu",
      element: <Pokemon />,
    },
  ]);
  return (
    <div>
      <BrowserRouter>
        <Pokemons />
      </BrowserRouter>
      <RouterProvider router={router} />
    </div>
  );
}

export default App;

Pokemons.jsx:

import React, { useEffect, useState } from "react";
import "./App.css";
import { Link, RouterProvider, createBrowserRouter } from "react-router-dom";
import Pokemon from "./Pokemon.jsx";

function Pokemons() {
  const [pokemonDetails, setPokemonDetails] = useState([]);
  const [routerPath, setRouterPath] = useState(["o"]);
  
  
  
  useEffect(() => {
    async function fetchData() {
      const response = await fetch(
        "https://pokeapi.co/api/v2/pokemon?limit=20"
      );
      const data = await response.json();
      setPokemonDetails(
        await Promise.all(data.results.map((p) => fetchPokemonDetails(p.url)))
      );
      console.log(pokemonDetails);
    }
    fetchData();
  }, []);

  async function fetchPokemonDetails(url) {
    const response = await fetch(url);
    const data = await response.json();
    return data;
  }



  return (
    <div>
      <h2>Pokemony</h2>
      
      <div id="pokemony">
        {pokemonDetails.map((element, index) => (
          <div className="jedenBlok" key={index}>
            <h2 id={"Napis " + element.name} className="pokemoN">
              {element.name}
            </h2>
            <img
              src={element.sprites.front_default}
              alt={`Pokemon ${index + 1}`}
              id={"Pokemon " + element.name}
              className="pokemon"
            />
            <Link to="/pikachu">link</Link>
          </div>
        ))}
      </div>
    </div>
  );
}

export default Pokemons;

Pokemon.jsx:

import React, { useEffect, useState } from "react";
import { Link } from "react-router-dom";

function Pokemon() {
  const [pokemon, setPokemon] = useState([]);

  useEffect(() => {
    async function fetchData() {
      const response = await fetch(
        "https://pokeapi.co/api/v2/pokemon?limit=20"
      );
      const data = await response.json();
      setPokemon(
        await Promise.all(data.results.map((p) => fetchPokemonDetails(p.url)))
      );
    }
    fetchData();
  }, []);

  async function fetchPokemonDetails(url) {
    const response = await fetch(url);
    const data = await response.json();
    return data;
  }

  return (
    <div>
      <Link to="/">main</Link>
      {pokemon.map((pokemon, index) => (
        <div key={index}>
          <h1>{pokemon.name}</h1>
        </div>
      ))}
    </div>
  );
}

export default Pokemon;

2

Answers


  1. Update the router to render Pokemon on a single route with a dynamic route path.

    import React from "react";
    import {
      createBrowserRouter,
      RouterProvider,
    } from "react-router-dom";
    import Pokemons from "./Pokemons";
    import Pokemon from "./Pokemon";
    
    function App() {
      const router = createBrowserRouter([
        {
          path: "/",
          element: <Pokemons />,
        },
        {
          path: "/:pokemonName",
          element: <Pokemon />,
        },
      ]);
      return (
        <div>
          <RouterProvider router={router} />
        </div>
      );
    }
    
    export default App;
    

    Update Pokemons to pass the details in route state to the Pokemon page.

    function Pokemons() {
      const [pokemonDetails, setPokemonDetails] = useState([]);
      
      useEffect(() => {
        async function fetchData() {
          const response = await fetch(
            "https://pokeapi.co/api/v2/pokemon?limit=20"
          );
          const data = await response.json();
          setPokemonDetails(
            await Promise.all(data.results.map((p) => fetchPokemonDetails(p.url)))
          );
          console.log(pokemonDetails);
        }
        fetchData();
      }, []);
    
      async function fetchPokemonDetails(url) {
        const response = await fetch(url);
        const data = await response.json();
        return data;
      }
    
      return (
        <div>
          <h2>Pokemony</h2>
          
          <div id="pokemony">
            {pokemonDetails.map((element) => (
              <div className="jedenBlok" key={element.id}>
                <h2 id={"Napis " + element.name} className="pokemoN">
                  {element.name}
                </h2>
                <img
                  src={element.sprites.front_default}
                  alt={`Pokemon ${element.name}`}
                  id={"Pokemon " + element.name}
                  className="pokemon"
                />
                <Link
                  to={`/${element.name}`}      // target dynamic path
                  state={{ details: element }} // pass data
                >
                  link
                </Link>
              </div>
            ))}
          </div>
        </div>
      );
    }
    

    Update Pokemons to read the route state.

    import React, { useEffect, useState } from "react";
    import { useLocation, useParams } from "react-router-dom";
    
    function Pokemon() {
      const { pokemonName } = useParams();
      const { state } = useLocation();
      const { details } = state || {};
    
      return (
        <div>
          <Link to="/">main</Link>
          <div>
            <h1>{pokemonName}</h1>
            {details && (
              // render pokemon details
            )}
          </div>
        </div>
      );
    }
    
    export default Pokemon;
    
    Login or Signup to reply.
  2. I would not gather all data ahead of time. You can manually form an image URL from the ID within the URL values that come back from the paged response.

    The only time you should request the full data for a Pokémon should be when you are viewing that particular Pokémon. It is expensive to make n-number of calls back to the API to stitch it all together.

    App.jsx

    This is where the routing occurs. Your app contains a <RouterProvider>.

    import React from "react";
    import { RouterProvider, createBrowserRouter } from "react-router-dom";
    import PokemonList from "./components/PokemonList";
    import PokemonDetails from "./components/PokemonDetails";
    
    function App() {
      const router = createBrowserRouter([
        {
          path: "/",
          element: <PokemonList />,
        },
        {
          path: "/:pokemonName",
          element: <PokemonDetails />,
        },
      ]);
      return (
        <div className="App">
          <h1 style={{ textAlign: "center" }}>Pokemon App</h1>
          <RouterProvider router={router} />
        </div>
      );
    }
    
    export default App;
    

    components/PokemonList.jsx

    This is the main "home" route that lists Pokémon via an offset and limit.

    If you wanted only the legendary birds, you could load the following URL:

    http://localhost:5173/?offset=143&limit=3

    import React, { useEffect, useState } from "react";
    import PokemonListItem from "./PokemonListItem";
    import { fetchPokemon, getNumberOrDefault } from "../api/PokemonService";
    import { useSearchParams } from "react-router-dom";
    
    function PokemonList() {
      const [searchParams] = useSearchParams();
    
      const [offset, setOffset] = useState(null);
      const [limit, setLimit] = useState(null);
      const [pokemonData, setPokemonData] = useState(null);
    
      // Listen to changes in the URL search params
      useEffect(() => {
        setOffset(getNumberOrDefault(searchParams.get("offset"), 0));
        setLimit(getNumberOrDefault(searchParams.get("limit"), 20));
      }, [searchParams]);
    
      // Fetch the Pokemon data
      useEffect(() => {
        (async () => {
          if (offset === null || limit === null) {
            return; // These are required
          }
          const details = await fetchPokemon(offset, limit);
          setPokemonData(details);
        })();
      }, [limit, offset]);
    
      return (
        <div className="PokemonList">
          <h2 style={{ textAlign: "center" }}>Pokemon List</h2>
          {pokemonData != null ? (
            <div className="PokemonList-Grid">
              {pokemonData.map(({ dex, name }) => (
                <PokemonListItem key={dex} dex={dex} name={name} />
              ))}
            </div>
          ) : (
            <p>Loading...</p>
          )}
        </div>
      );
    }
    
    export default PokemonList;
    

    components/PokemonListItem.jsx

    This is the Pokémon tile component that gets rendered in the list (grid) on the "home" route.

    import React from "react";
    import { Link } from "react-router-dom";
    import PropTypes from "prop-types";
    import { getSpriteByDex } from "../api/PokemonService";
    
    function PokemonListItem({ dex, name }) {
      return (
        <div className="PokemonListItem" key={dex}>
          <h3 style={{ textTransform: "capitalize" }}>{name}</h3>
          <img src={getSpriteByDex(dex)} alt={`Pokemon ${name}`} />
          <Link to={`/${name}`}>Details</Link>
        </div>
      );
    }
    
    PokemonListItem.propTypes = {
      dex: PropTypes.number.isRequired,
      name: PropTypes.string.isRequired,
    };
    
    export default PokemonListItem;
    

    component/PokemonDetails.jsx

    This is a thorough detail page explaining the selected Pokémon.

    import React, { useEffect, useState } from "react";
    import { Link, useParams } from "react-router-dom";
    import { fetchPokemonDetails } from "../api/PokemonService";
    
    function PokemonDetails() {
      const { pokemonName } = useParams();
    
      const [details, setDetails] = useState(null);
    
      useEffect(() => {
        (async () => {
          const data = await fetchPokemonDetails(pokemonName);
          setDetails(data);
        })();
      }, [pokemonName]);
    
      return (
        <div className="PokemonDetails">
          <Link to="/">Go Back</Link>
          <div>
            {details != null ? (
              <>
                <h2 style={{ textTransform: "capitalize" }}>{details.name}</h2>
                <img
                  src={details.sprites.front_default}
                  alt={`Pokemon ${details.name}`}
                />
                <h3>Size</h3>
                <ul>
                  <li>Weight: {details.weight} kg</li>
                  <li>Height: {details.height} m</li>
                </ul>
                <h3>Types</h3>
                <ul>
                  {details.types.map(({ type }) => (
                    <li key={type.name}>{type.name}</li>
                  ))}
                </ul>
                <h3>Abilities</h3>
                <ul>
                  {details.abilities.map(({ ability }) => (
                    <li key={ability.name}>{ability.name}</li>
                  ))}
                </ul>
              </>
            ) : (
              <p>{`Loading data for ${pokemonName}...`}</p>
            )}
          </div>
        </div>
      );
    }
    
    export default PokemonDetails;
    

    api/PokemonService.js

    This is the central business logic for fetching Pokémon information.

    const API_HOST = "https://pokeapi.co";
    const API_PATH = "api/v2";
    
    const API_SPRITE_BASE_URL =
      "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon";
    
    // Extract the ID (Pokedex number) from the URL
    const ID_REGEX = new RegExp(
      `^${escapeRegExp(`${API_HOST}/${API_PATH}/pokemon/`)}(\d+)(?:\/)?$`
    );
    
    function escapeRegExp(string) {
      return string.replace(/[/-\^$*+?.()|[]{}]/g, "\$&");
    }
    
    function getRequestUrl(path, params = {}) {
      const url = new URL(API_HOST);
      url.pathname = `${API_PATH}/${path}`;
      for (let param in params) {
        url.searchParams.append(param, params[param]);
      }
      return url.toString();
    }
    
    function getSpriteByDex(dex) {
      return `${API_SPRITE_BASE_URL}/${dex}.png`;
    }
    
    function getNumberOrDefault(value, defaultValue) {
      const parsedValue = parseInt(value, 10);
      return isNaN(parsedValue) ? defaultValue : parsedValue;
    }
    
    async function fetchPokemon(offset, limit) {
      const endpoint = getRequestUrl("pokemon", { offset, limit });
      const response = await fetch(endpoint);
      const { results } = await response.json();
      return results.map(({ name, url }) => {
        const [, id] = url.match(ID_REGEX);
        return { dex: parseInt(id, 10), name };
      });
    }
    
    async function fetchPokemonDetails(pokemonName) {
      const endpoint = getRequestUrl(`pokemon/${pokemonName}`);
      const response = await fetch(endpoint);
      return response.json();
    }
    
    export {
      getNumberOrDefault,
      getSpriteByDex,
      fetchPokemon,
      fetchPokemonDetails,
    };
    

    index.css

    The basic styles for the Pokémon list.

    .PokemonList-Grid {
      display: grid;
      grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
      gap: 1rem;
      padding: 1rem;
    }
    
    .PokemonListItem {
      display: flex;
      flex-direction: column;
      align-items: center;
      padding: 1rem;
      border: 1px solid #ccc;
      border-radius: 5px;
      box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
    }
    

    package.json

    You will need React Router V6 and PropTypes to run the code above.

    {
      "dependencies": {
        "prop-types": "^15.8.1",
        "react": "^18.2.0",
        "react-dom": "^18.2.0"
      },
      "devDependencies": {
        "react-router-dom": "^6.22.3"
      }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search