0

I am learning React as I am fetching data from Pokéapi to make a list component, card component, detail component and filter component. I am trying to make a filter so you can filter by pokémon type. Only the cards that also contain that type string should then render (Not there yet). So I am not sure if a) I should make a different call from API inside PokemonList depending on selected value or b) if I should compare the values and just change how the PokemonCard element is rendered inside PokemonList.js depending on the comparison. I managed to pass data from filter to the list component. I have then been trying to pass the type data from PokemonCard.js to the list component so that I can compare these two values but I find it hard to use callbacks to pass the type data from the card component, since I dont pass it through an event or something like that.

enter image description here

Which method should I use here to simplify the filtering? Make different API call or render PokemonCard element conditionally? Is it a good idea to compare filter option to pokemon card's type in PokemonList.js? Then how can I pass that data from the card component since I don't pass it through click event?

Thankful for any ideas! I paste the code from list component that contains the cards, card component and filter component.

PokemonList component:

import { useState } from 'react';
import useSWR from 'swr';
import PokemonCard from './PokemonCard';
import PokemonFilter from './PokemonFilter';
import './PokemonList.css';

const PokemonList = () => {

const [index, setIndex] = useState(0);
const [type, setType] = useState('');

function selectedType(type) { // value from filter dropdown
    setType(type)
    console.log("handled")
    console.log(type)
}

const url = `https://pokeapi.co/api/v2/pokemon?limit=9&offset=${index}`;

const fetcher = (...args) => fetch(...args).then((res) => res.json())
const { data: result, error } = useSWR(url, fetcher);

if (error) return <div>failed to load</div>
if (!result) return <div>loading...</div>

result.results.sort((a, b) => a.name < b.name ? -1 : 1);


return (
    <section>
        <PokemonFilter onSelectedType={selectedType} selectedPokemonType={type} />
        <div className="pokemon-list">
            <div className="pokemons">
                {result.results.map((pokemon) => (
                    <PokemonCard key={pokemon.name} pokemon={pokemon} /> // callback needed??
                ))}
            </div>
            <div className="pagination">
                <button 
                    onClick={() => setIndex(index - 9)} 
                    disabled={result.previous === null}
                >
                Previous
                </button>
                <button 
                    onClick={() => setIndex(index + 9)}
                    disabled={result.next === null}
                >
                Next
                </button>
            </div>
        </div>
    </section>
  )
}

export default PokemonList;

PokemonCard component:

import { Link } from "react-router-dom";
import useSWR from 'swr';
import './PokemonCard.css';

const PokemonCard = ({ pokemon }) => {

const { name } = pokemon;

const url = `https://pokeapi.co/api/v2/pokemon/${name}`;
const { data, error } = useSWR(url);

if (error) return <div>failed to load</div>
if (!data) return <div>loading...</div>

const { types, abilities } = data;

// types[0].type.name <---- value I want to pass to PokemonList.js

return (
        <div className='pokemon-card'>
            <div className='pokemon-card__content'>
                <img
                    className='pokemon-card__image'
                    src={data.sprites.front_default}
                    alt={name}
                />
                <div className='pokemon-card__info'>  
                <p className='pokemon-card__name'>Name: {name}</p>
                <p className='pokemon-card__abilities'>Abilities: {abilities[0].ability.name}</p>
                <p className='pokemon-card__categories'>Category: {types[0].type.name}</p> 
                </div>
            </div>
            <Link className='pokemon-card__link' to={{
                pathname: `/${name}`,
                state: data
                }}>
                View Details
            </Link>
        </div>
  )
}

export default PokemonCard;

PokemonFilter component:

import './PokemonFilter.css';
import useSWR from 'swr';

const PokemonFilter = ({onSelectedType, selectedPokemonType}) => {

const url = `https://pokeapi.co/api/v2/type/`;

const fetcher = (...args) => fetch(...args).then((res) => res.json())
const { data: result, error } = useSWR(url, fetcher);

if (error) return <div>failed to load</div>
if (!result) return <div>loading...</div>

function filteredTypeHandler(e) {
    console.log(e.target.value);
    onSelectedType(e.target.value);
}

console.log(selectedPokemonType)

return(
    <div className="pokemon-types__sidebar">
        <h2>Filter Pokémon by type</h2>
        <select 
            name="pokemon-type" 
            className="pokemon-types__filter"
            onChange={filteredTypeHandler}
            >
            <option value="All">Filter By Type</option>
            {result.results.map((type) => {
            return (
                <option key={type.name} value={type.name}> {type.name}</option>
                )
            })}
        </select>
    </div>
  )
}

export default PokemonFilter;
hawe
  • 15
  • 5
  • You should use useEffect. Each time your PokemonFilter component changes (when a category is selected) you will make a callback to the 'fetcher' function and you save the response in a state (an array). Then you use the map() function to display the cards. – MB_ Sep 03 '21 at 19:04
  • Each time your PokemonFilter component changes => Each time the "type" state changes – MB_ Sep 03 '21 at 19:13
  • Thank you very much for your input! I will try to modify the code like you suggested tomorrow. I’ll get back to you to let you know the result, or if it’s okay to ask for more input. – hawe Sep 03 '21 at 19:25
  • @MB_ What do you mean by saving the response in a state? I am making separate calls to the API, so which response should I save in a state and how can I combine these two calls so that the list renders depending on the state of type? I can't use useSWR inside of useEffect so I am not sure how to combine useEffect with useSWR. Or is the callback you proposed I'd use a solution to that? I am not really following. I also read in SWR docs and saw that I could implement condional fetching. Do you think that could be an alternative? – hawe Sep 05 '21 at 13:35

1 Answers1

1

Here is an example to improve, modify, ... I didn't test, it's just a visual example.

I don't know about useSWR sorry, I use axios in my example...

If you want to centralize all your API requests, you can create a useApi hook, on the internet you will find tutorials.

PokemonList.js

import React, { useState, useEffect } from 'react';
import axios from 'axios';                           // or swr

import PokemonFilter from './PokemonFilter';
import PokemonCard from './PokemonCard';

export default function PokemonList() {
  const [data, setData] = useState([]);
  const [filter, setFilter] = useState('');

  // Executed every first render
  useEffect(() => {
    getData();
  }, []);

  // Executed only when filter changes
  useEffect(() => {
    getDataByTypes(filter);
  }, [filter]);

  // Get data
  const getData = async () => {
    const uri = 'https://xxx';

    try {
      const response = await axios.get(uri);
      setData(response.data...);
    } catch (error) {
      console.log(error);
    }
  };

  // Get data by types
  const getDataByTypes = async (filter) => {
    const uri = `https://xxx/type/${filter}...`;

    if (filter) {
      try {
        const response = await axios.get(uri);
        setData(response.data...);
      } catch (error) {
        console.log(error);
      }
    }
  };

  return (
    <div className="main">
      <PokemonFilter filter={filter} setFilter={setFilter} />
      <div className="container">
        <div className="cards-container">
          {data.map((d) => (
            <PokemonCard key={d.name} data={d} />
          ))}
        </div>
      </div>
    </div>
  );
}

PokemonCard.js

import React, { useState, useEffect } from 'react';
import axios from 'axios';

export default function PokemonCard({ data }) {
  const [pokemons, setPokemons] = useState();

  useEffect(() => {
    getPokemons(data);
  }, [data]);

  // Get Pokemons
  const getPokemons = async (data) => {
    const uri = `https://xxx/pokemon/${data.name}/`;

    try {
      const response = await axios.get(uri);
      setPokemons(response.data...);
    } catch (error) {
      console.log(error);
    }
  };

  return (
    <div>
      {pokemons && (
        <div className="card">
          <img src={pokemons.sprites.front_default} alt={pokemons.name} />
          <p>{pokemons.name}</p>
          <p>{pokemons.abilities[0].ability.name}</p>
          <p>{pokemons.types[0].type.name}</p>
        </div>
      )}
    </div>
  );
}

PokemonFilter.js

import React, { useState, useEffect } from 'react';
import axios from 'axios';

export default function PokemonFilter({ filter, setFilter }) {
  const [types, setTypes] = useState([]);

  useEffect(() => {
    getType();
  }, []);

  // Get Type
  const getType = async () => {
    const uri = 'https://xxx/type/';

    try {
      const response = await axios.get(uri);
      setTypes(response.data.results....);
    } catch (error) {
      console.log(error);
    }
  };

  const handleFilter = (e) => {
    setFilter(e.target.value);
  };

  return (
    <select onChange={handleFilter} value={filter}>
      <option>Filter by type</option>
      {types.map((type) => {
        return (
          <option key={type.name} value={type.name}>
            {type.name}
          </option>
        );
      })}
    </select>
  );
}
MB_
  • 1,667
  • 1
  • 6
  • 14
  • Thank you for taking the time to translate this into an answer in code. I had trouble with translating this into SWR so I am currently trying to use Axios instead. Now I get an error once I click a type in the filter, so I am guessing it has to to with the data that hasn't been fetched yet and React is trying to map through it before that. But it seems easier to find a solution to that error than to solve this with SWR, so I will probably keep going with Axios or fetch from now on. :) @MB_ – hawe Sep 09 '21 at 12:02