1

tl;dr new to coding. 1 card = 1 pokémon name. Mapped out 898+ cards, listed in increments of 20. How can I set a modal to pop up when a card is clicked, and have it display the data in Pokemon.js?

Currently a developer in training. I have individual pokémon names mapped out onto 898+ cards (paginated 20 at a time). When a specific card is clicked, it loads that pokémon's information and wipes out the list of cards. When you go back to the main page, the names reset back to the first 20. How do I implement modals to load the pokémon's information instead when a card is clicked? I've watched a bunch of tutorials but am a bit confused as to where to even place the modal in the build (I have components on top of components). I’m also trying to avoid adding buttons on the cards for designating an onClick. I'm assuming this process isn't as complex as I'm making it, but any instruction on how to do it based on the code I've provided would be immensely appreciated. I'll include the individual components and my App.js file.

App.js

import React from 'react';
import { HashRouter as Router, Route, Switch } from 'react-router-dom';
import NavBar from '../src/components/layout/NavBar';
import Dashboard from './components/layout/Dashboard';
import Pokemon from './components/pokemon/Pokemon';
import backgroundImage from './pokeball.png';
import 'bootstrap/dist/css/bootstrap.min.css';
import './App.css';

function App() {
  return (
    <Router>
      <div 
        className='App' 
        style={{
          background: `url(${backgroundImage})`
        }}
      >
        <NavBar />
        <div className='container'>
          <Switch>
            <Route 
              exact path='/' 
              component={Dashboard} 
            />
            <Route 
              exact path='/pokemon/:indexNum'
              render={(props) => <Pokemon {...props} />}
            />
          </Switch>
        </div>
      </div>
    </Router>
  );
}

export default App;

Dashboard.js

import React from 'react';
import PokemonList from '../pokemon/PokemonList';

const Dashboard = () => {
  return (
    <div className='row'>
      <div 
        className='col'
        style={{
          marginTop: '25px'
        }}
      >
        <PokemonList />
      </div>
      
    </div>
  )
}

export default Dashboard

PokemonList.js

import React, { useState, useEffect } from 'react';
import PokemonCard from './PokemonCard';
import Pagination from '../layout/Pagination';
import axios from 'axios';

const PokemonList = () => {
  const [pokemon, setPokemon] = useState([])
  const [currentPageUrl, setCurrentPageUrl] = useState('https://pokeapi.co/api/v2/pokemon')
  const [loading, setLoading] = useState(true)
  const [nextPageUrl, setNextPageUrl] = useState()
  const [prevPageUrl, setPrevPageUrl] = useState()

  useEffect(() => {
    setLoading(true)
    let cancel
    axios.get(currentPageUrl, {
      cancelToken: new axios.CancelToken(c => cancel = c)
    }).then(res => {
      setLoading(false)
      // console.log(res.data.results)
      setPokemon(res.data.results)
      setNextPageUrl(res.data.next)
      setPrevPageUrl(res.data.previous)
    })

    return () => cancel()

  }, [currentPageUrl])

  const goToNextPage = () => {
    setCurrentPageUrl(nextPageUrl)
  }

  const goToPrevPage = () => {
    setCurrentPageUrl(prevPageUrl)
  }

  if (loading) return "Loading Your Pokémon..."

  return (
    <>
      {pokemon ? (
        <div className='row'>
          {pokemon.map((i) => (
            <PokemonCard
              key={i.name} 
              pokemon={i.name}
              url={i.url}
            />
          ))}
        </div>
      ) : (
        <h1>Loading</h1>
      )}
        <div className='row'>
          <Pagination
            goToNextPage={nextPageUrl ? goToNextPage : null}
            goToPrevPage={prevPageUrl ? goToPrevPage : null}
          />
        </div>  
    </>
  )
}

export default PokemonList

Pagination.js

import React from 'react';
import { ChevronDoubleLeft, ChevronDoubleRight } from 'react-bootstrap-icons';

const Pagination = ({ goToNextPage, goToPrevPage }) => {
  return (
    <div className='mx-auto'>
      {
        goToPrevPage && 
        <button
          className='badge badge-pill badge-danger'
          type='button'
          onClick={goToPrevPage}
          style={{
            marginBottom: '20px',
            padding: '10px'
          }}
        >
          <ChevronDoubleLeft />
        </button>
      }
      <span>
        &nbsp;&nbsp;&nbsp;
      </span>
      {
        goToNextPage && 
        <button
          className='badge badge-pill badge-danger'
          type='button'
          onClick={goToNextPage}
          style={{
            marginBottom: '20px',
            padding: '10px'
          }}
        >
          <ChevronDoubleRight />
        </button>
      }
    </div>
  )
}

export default Pagination

PokemonCard.js

import React, { useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import axios from 'axios';
import styled from 'styled-components';
import gif from '../../loading-wheel.gif';

const Sprite = styled.img`
  width: 5em;
  height: 5em;
`;

const Card = styled.div`
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
  transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
  &:hover {
    box-shadow: 0 14px 28px rgba(0, 0, 0, 0.25), 0 10px 10px rgba(0, 0, 0, 0.22);
  }
  -moz-user-select: none;
  -website-user-select: none;
  user-select: none;
  -o-user-select: none;
`;

const StyledLink = styled(Link)`
  text-decoration: none;
  color: black; 
  &:focus,
  &:hover,
  &:visited,
  &:link,
  &:active {
    text-decoration: none;
  }
`;

const PokemonCard = ({ pokemon, url }) => {
  const [sprite, setSprite] = useState('')
  const [loadImage, setLoadImage] = useState(true)
  const [tooManyRequests, setTooManyRequests] = useState(false)
  const [didMount, setDidMount] = useState(false)
  const [indexNum] = useState(url.split('/')[url.split('/').length - 2])

  useEffect(() => {
    axios.get('https://pokeapi.co/api/v2/pokemon/' + pokemon)
      .then (res => {
        setSprite(res.data.sprites.front_default)
      })
      setDidMount(true);
      return () => setDidMount(false)
  }, [pokemon])

  if (!didMount) {
    return null;
  }

  return (
    <div className='col-md-3 col-sm-6 mb-5'>
      <StyledLink to={`pokemon/${indexNum}`}>
        <Card className='card'>
          <p className='card-header'>
            <img 
              src='https://icon-library.com/images/small-pokeball-icon/small-pokeball-icon-4.jpg' 
              alt='Pokéball Icon'
              width='40px'
              height='31.75px'
            />
            {indexNum}
          </p>
          {loadImage ? (
            <img
              className='card-img-top rounded mx-auto d-block mt-2'
              src={gif}
              alt='Loading'
              style={{
                width: '5em',
                height: '5em'
              }}
            />
          ) : null}
          <div className='card-body'>
              <center>
                <Sprite
                className='card-img-top rounded mx-auto mt-2' 
                src={sprite} 
                alt='Pokémon Sprite'
                onLoad={() => setLoadImage(false)}
                onError={() => setTooManyRequests(true)}
                style={
                  tooManyRequests ? { display: 'block'} :
                  loadImage ? null : { display: 'block'}
                }
                />
                <h6>
                  {pokemon
                    .toLowerCase()
                    .split('-')
                    .map(letter => letter.charAt(0).toUpperCase() + letter.substring(1))
                    .join(' ')
                  }
                </h6>
              </center>
          </div>
        </Card>
      </StyledLink>
    </div>
  )
  
}

export default PokemonCard

Pokemon.js

import React, { useState, useEffect } from 'react';
import axios from 'axios';
import gif from '../../loading-wheel.gif';

const type_colors = {
  bug: 'B1C12E',
  dark: '4F3A2D',
  dragon: '755EDF',
  electric: 'FCBC17',
  fairy: 'F4B1F4',
  fighting: '823551D',
  fire: 'E73B0C',
  flying: 'A3B3F7',
  ghost: '6060B2',
  grass: '74C236',
  ground: 'D3B357',
  ice: 'A3E7FD',
  normal: 'C8C4BC',
  poison: '934594',
  psychic: 'ED4882',
  rock: 'B9A156',
  steel: 'B5B5C3',
  water: '3295F6'
};

const Pokemon = (props) => {
  const { match } = props
  const { params } = match
  const { indexNum } = params
  
  const [pokemon, setPokemon] = useState(undefined)
  const [bio, setBio] = useState('')
  const [genderRatioMale, setGenderRatioMale] = useState(0)
  const [genderRatioFemale, setGenderRatioFemale] = useState(0)
  const [catchRate, setCatchRate] = useState(0)
  const [growthRate, setGrowthRate] = useState('')
  const [hatchSteps, setHatchSteps] = useState(0)

  useEffect(() => {
    axios.get(`https://pokeapi.co/api/v2/pokemon/${indexNum}/`)
      .then (res => {
        const { data } = res
        // console.log(data)
        setPokemon(data)
      })
      .catch(err => {
        setPokemon(false)
      })
  }, [indexNum])

  useEffect(() => {
    axios.get(`https://pokeapi.co/api/v2/pokemon-species/${indexNum}/`)
      .then(res => {
        const { data } = res
        let description = '';
        // console.log(data)
        data.flavor_text_entries
          .some(flavor => {
            const { flavor_text, language } = flavor
            const { name } = language
            if (name === 'en') {
              description = flavor_text
              // console.log(description)
              setBio(description)
            }
            const affirm1 = console.log('Description set as state for bio')
            return affirm1
          })

        const femaleRate = data.gender_rate
        setGenderRatioFemale(12.5 * femaleRate)
        setGenderRatioMale(12.5 * (8 - femaleRate))

        setCatchRate(data.capture_rate)

        setGrowthRate(data.growth_rate.name)

        setHatchSteps(255 * (data.hatch_counter) + 1)
      })
  }, [indexNum])

  const loadPokemonJsx = () => {
    const { 
      name, 
      id, 
      abilities,  
      height, 
      weight,  
      types, 
      stats, 
      sprites
    } = pokemon

    const {front_default} = sprites
    const fullImageUrl = `https://pokeres.bastionbot.org/images/pokemon/${id}.png`

    let [
      hp, 
      attack, 
      defense, 
      speed, 
      specialAttack, 
      specialDefense
    ] = ''

    stats.map(stat => {
      switch (stat.stat.name) {
        case 'hp':
          hp = stat['base_stat'];
          // console.log(`HP: ${hp}`)
          break;
        case 'attack':
          attack = stat['base_stat'];
          // console.log(`ATK: ${attack}`)
          break;
        case 'defense':
          defense = stat['base_stat'];
          // console.log(`DEF: ${defense}`)
          break;
        case 'speed':
          speed = stat['base_stat'];
          // console.log(`SPEED: ${speed}`)
          break;
        case 'special-attack':
          specialAttack = stat['base_stat'];
          // console.log(`SP. ATK: ${specialAttack}`)
          break;
        case 'special-defense':
          specialDefense = stat['base_stat'];
          // console.log(`SP. DEF: ${specialDefense}`)
          break;
        default:
          break;
      }
      const affirm2 = console.log('Variables from switch case are set')
      return affirm2
    })

    return (
      <>
        <div 
          className='col'
          style={{
            marginTop: '25px'
          }}
        >
          <div className='card'>
            <div className='card-header'>
              <div className='row'>
                <div className='col-5'>
                  <h5>
                    <img 
                      src='https://icon-library.com/images/small-pokeball-icon/small-pokeball-icon-4.jpg' 
                      alt='Pokéball Icon'
                      width='45.714px'
                      height='36.285px'
                    />
                    {indexNum}
                  </h5>
                </div>
                <div className='col-7'>
                  <div 
                    style={{
                      display: 'flex',
                      flexDirection: 'row-reverse'
                    }}
                  >
                    { types.map(typeInfo => {
                      const { type } = typeInfo;
                      const { name } = type;
                      return <span 
                        key={name}
                        className='badge badge-primary badge-pill mr-1'
                        style={{
                          backgroundColor: `#${type_colors[name]}`,
                          color: 'white'
                        }}
                      >
                        {`${name
                            .toLowerCase()
                            .split(' ')
                            .map(letter => letter.charAt(0).toUpperCase() + letter.substring(1))
                            .join(' ')
                          }`
                        }
                      </span>
                    }) }
                  </div>
                </div>
              </div>
            </div>
            <div className='card-body'>
              <div className='row align-items-center'>
                <div className='col-md-3'>
                  <img
                    src={fullImageUrl}
                    alt='Pokémon Large Pic' 
                    className='card-img-top rounded mx-auto mt-2'
                  />  
                </div>
                <div className='col-md-9'>
                  <h5 className='mx-auto'>
                    { name
                      .toLowerCase()
                      .split('-')
                      .map(letter => letter.charAt(0).toUpperCase() + letter.substring(1))
                      .join(' ')
                    }
                    <img
                      src={front_default}
                      alt='Pokémon Sprite' 
                    />
                  </h5>
                  <div className='row align-items-center'>
                    <div className='col-12 col-md-3'>HP</div>
                    <div className='col-12 col-md-9'>
                      <div className='progress'>
                        <div 
                          className='progress-bar'
                          role='progressbar'
                          style={{
                            width: `${hp}%`
                          }}
                          aria-valuenow='25'
                          aria-valuemin='0'
                          aria-valuemax='100'
                        >
                          <small>{hp}</small>
                        </div>
                      </div>
                    </div>
                  </div>
                  <div className='row align-items-center'>
                    <div className='col-12 col-md-3'>Attack</div>
                    <div className='col-12 col-md-9'>
                      <div className='progress'>
                        <div 
                          className='progress-bar'
                          role='progressbar'
                          style={{
                            width: `${attack}%`
                          }}
                          aria-valuenow='25'
                          aria-valuemin='0'
                          aria-valuemax='100'
                        >
                          <small>{attack}</small>
                        </div>
                      </div>
                    </div>
                  </div>
                  <div className='row align-items-center'>
                    <div className='col-12 col-md-3'>Sp. Attack</div>
                    <div className='col-12 col-md-9'>
                      <div className='progress'>
                        <div 
                          className='progress-bar'
                          role='progressbar'
                          style={{
                            width: `${specialAttack}%`
                          }}
                          aria-valuenow='25'
                          aria-valuemin='0'
                          aria-valuemax='100'
                        >
                          <small>{specialAttack}</small>
                        </div>
                      </div>
                    </div>
                  </div>
                  <div className='row align-items-center'>
                    <div className='col-12 col-md-3'>Defense</div>
                    <div className='col-12 col-md-9'>
                      <div className='progress'>
                        <div 
                          className='progress-bar'
                          role='progressbar'
                          style={{
                            width: `${defense}%`
                          }}
                          aria-valuenow='25'
                          aria-valuemin='0'
                          aria-valuemax='100'
                        >
                          <small>{defense}</small>
                        </div>
                      </div>
                    </div>
                  </div>
                  <div className='row align-items-center'>
                    <div className='col-12 col-md-3'>Sp. Defense</div>
                    <div className='col-12 col-md-9'>
                      <div className='progress'>
                        <div 
                          className='progress-bar'
                          role='progressbar'
                          style={{
                            width: `${specialDefense}%`
                          }}
                          aria-valuenow='25'
                          aria-valuemin='0'
                          aria-valuemax='100'
                        >
                          <small>{specialDefense}</small>
                        </div>
                      </div>
                    </div>
                  </div>
                  <div className='row align-items-center'>
                    <div className='col-12 col-md-3'>Speed</div>
                    <div className='col-12 col-md-9'>
                      <div className='progress'>
                        <div 
                          className='progress-bar'
                          role='progressbar'
                          style={{
                            width: `${speed}%`
                          }}
                          aria-valuenow='25'
                          aria-valuemin='0'
                          aria-valuemax='100'
                        >
                          <small>{speed}</small>
                        </div>
                      </div>
                    </div>
                  </div>
                </div>
                <div className='row mt-2'>
                  <div className='col'>
                    <p className='p-2'>{bio}</p>
                  </div>
                </div>
              </div>
            </div>
            <hr />
            <div className='card-body'>
              <h5 className='card-title text-center'>Profile</h5>
              <div className='row'>
                <div className='col-md-6'>
                  <div className='row flex-container'>
                    <div className='col-md-6'>
                      <h6 style={{display: 'flex'}}>Approx. Height:</h6>
                    </div>
                    <div className='col-md-6'>
                      <h6 style={{
                        display: 'flex',
                        flexDirection: 'row-reverse'
                      }}
                      >
                        {  
                          (Math.round((height * 0.328084 + 0.0001) * 100) / 100) + ' ft / ' + 
                          (Math.round((height * 10 + 0.0001) * 100) / 100) + ' cm' 
                        }
                      </h6>
                    </div>
                  </div>
                  <div className='row flex-container'>
                    <div className='col-md-6'>
                      <h6 style={{display: 'flex'}}>Approx. Weight:</h6>
                    </div>
                    <div className='col-md-6'>
                      <h6 style={{
                        display: 'flex',
                        flexDirection: 'row-reverse'
                      }}
                      >
                        {  
                          (Math.round((weight * 0.220462 + 0.0001) * 100) / 100) + ' lbs / ' +
                          (Math.round((weight * 0.1 + 0.0001) * 100) / 100) + ' kg' 
                        }
                      </h6>
                    </div>
                  </div>
                  <div className='row flex-container'>
                    <div className='col-md-6'>
                      <h6 style={{display: 'flex'}}>Catch Rate:</h6>
                    </div>
                    <div className='col-md-6'>
                      <h6 style={{
                        display: 'flex',
                        flexDirection: 'row-reverse'
                      }}
                      >
                        { `${catchRate}%` }
                      </h6>
                    </div>
                  </div>
                  <div className='row flex-container'>
                    <div className='col-md-6'>
                      <h6 style={{display: 'flex'}}>Gender Ratio:</h6>
                    </div>
                    <div className='col-md-6'>
                      <div className='progress'>
                        <div 
                          className='progress-bar'
                          role='progressbar'
                          style={{
                            width: `${genderRatioFemale}%`,
                            backgroundColor: '#C2185B',
                          }}
                          aria-valuenow='15'
                          aria-valuemin='0'
                          aria-valuemax='100'
                        >
                          <small>{`${genderRatioFemale}%`}</small>        
                        </div>
                        <div 
                          className='progress-bar'
                          role='progressbar'
                          style={{
                            width: `${genderRatioMale}%`,
                            backgroundColor: '#1976D2'
                          }}
                          aria-valuenow='30'
                          aria-valuemin='0'
                          aria-valuemax='100'
                        >
                          <small>{`${genderRatioMale}%`}</small>        
                        </div>
                      </div>
                    </div>
                  </div>
                </div>
                <div className='col-md-6'>
                  <div className='row flex-container'>
                    <div className='col-md-6'>
                      <h6 style={{display: 'flex'}}>Growth Rate:</h6>
                    </div>
                    <div className='col-md-6'>
                      <h6 style={{
                        display: 'flex',
                        flexDirection: 'row-reverse'
                      }}
                      >
                        {growthRate
                          .toLowerCase()
                          .split('-')
                          .map(letter => letter.charAt(0).toUpperCase() + letter.substring(1))
                          .join(' ')
                        }
                      </h6>
                    </div>
                  </div>
                  <div className='row flex-container'>
                    <div className='col-md-6'>
                      <h6 style={{display: 'flex'}}>Hatch Steps:</h6>
                    </div>
                    <div className='col-md-6'>
                      <h6 style={{
                        display: 'flex',
                        flexDirection: 'row-reverse'
                      }}
                      >
                        {hatchSteps}
                      </h6>
                    </div>
                  </div>
                  <div className='row flex-container'>
                    <div className='col-md-6'>
                      <h6 style={{display: 'flex'}}>Effort Values:</h6>
                    </div>
                    <div className='col-md-6'>
                      <h6 style={{
                        display: 'flex',
                        flexDirection: 'row-reverse'
                      }}
                      >
                        {
                          stats.filter(stat => {
                            if (stat.effort > 0) {
                              return true;
                            }
                            return false;
                          }) 
                          .map(statInfo => {
                            const { effort, stat } = statInfo
                            const { name } = stat

                            return `${effort} ${name
                              .toLowerCase()
                              .split('-')
                              .map(letter => letter.charAt(0).toUpperCase() + letter.substring(1))
                              .join(' ')
                            }`
                          }).join(', ')
                        }
                      </h6>
                    </div>
                  </div>
                  <div className='row flex-container'>
                    <div className='col-md-6'>
                      <h6 style={{display: 'flex'}}>Abilities:</h6>
                    </div>
                    <div className='col-md-6'>
                      <h6 style={{
                        display: 'flex',
                        flexDirection: 'row-reverse'
                      }}
                      >
                        {
                          abilities.map(abilityInfo => {
                            const { ability } = abilityInfo;
                            const { name } = ability;
                            return `${name
                              .toLowerCase()
                              .split('-')
                              .map(letter => letter.charAt(0).toUpperCase() + letter.substring(1))
                              .join(' ')
                            }`
                          }).join(', ')
                        }
                      </h6>
                    </div>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </>
    )
  }

  return (
    <> 
      {pokemon === undefined && 
        <img 
          src={gif}
          alt='Loading'
        />
      }
      {pokemon !== undefined && pokemon && loadPokemonJsx(pokemon)}
      {pokemon === false && <h1> Pokémon Not Found</h1>} 
    </>
  )
}

export default Pokemon

Sorry this is such a long post. Thanks again!

0 Answers0