skip to Main Content

I’m using the Jikan API to create an anime gallery of some sort, one of the features I wanted to include was to be able to search for a specific anime title within the gallery. My issue is that with my current search input field; despite specifying a show, it will show the specific title you searched for along with a couple of duplicated titles of an entirely different show.

Another issue I’m having is that despite setting a limit to how many shows data will be fetched, once I clear the search field, those same duplicates will be added onto the gallery despite the limitation.

Image of gallery when user inputs a specific anime title

App.js

import './App.css';
import AnimeGallery from './components/AnimeGallery';
import Footer from './components/Footer';
import Header from './components/Header';
import NavFilter from './components/NavFilter';
import { useState, useEffect } from 'react';

function App() {
  const animeApi = 'https://api.jikan.moe/v4/top/anime?type=tv&sfw=true&limit=12&filter=airing';

  const [animeList, setAnimeList] = useState([]);
  const [filteredAnime, setFilteredAnime] = useState([])

  useEffect(() => {
    const fetchAnimeGenre = async () => {
      const result = await fetch(animeApi);
      const data = await result.json();
      setAnimeList(data.data);
      setFilteredAnime(data.data);
    };
    fetchAnimeGenre();
  }, []);


  return (
    <>
      <Header />
      <NavFilter animeList={animeList} setFilteredAnime={setFilteredAnime} />
      <AnimeGallery animeList={filteredAnime} />
      <Footer />
    </>
  );
}

export default App;

NavFilter.js

import '../styles/NavFilter.module.css'
import { useState } from 'react';

const NavFilter = ({ animeList, setFilteredAnime }) => {

    const [searchAnime, setSearchAnime] = useState('');

    const preventReload = (e) => {
        e.preventDefault();
    }

    const handleSearchNav = (e) => {
        const searchTitle = e.target.value;
        setSearchAnime(searchTitle);

        if (searchTitle === '') {
            setFilteredAnime(animeList);
        } else {

            const filteredResults = animeList.filter(anime =>
                anime.title.toLowerCase().includes(searchTitle.toLowerCase())
            );

            setFilteredAnime(filteredResults);
        };
    }



    return (
        <>
            <nav>
                <div className="w-full p-5 shadow-xl">
                    <ul className="flex justify-center tracking-wider text-sm">

                        <li>
                            <form
                                onSubmit={preventReload}
                            >
                                <input
                                    type="text"
                                    className="rounded-md px-8 py-1"
                                    placeholder="Search Anime..."
                                    value={searchAnime}
                                    onChange={handleSearchNav}></input>
                            </form>
                        </li>
                    </ul>
                </div>
            </nav>
        </>
    )
}

export default NavFilter;

AnimeGallery.js

import '../styles/AnimeGallery.module.css';

const AnimeGallery = ({ animeList }) => {

    return (
        <>
            <section className="anime-gallery container mx-auto grid sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 px-3 py-3 mt-20">
                {animeList.map((anime, index) => (
                    <article className="anime-card flex mx-3 my-2 rounded-lg h-72 bg-gray-dark shadow-md hover:-translate-y-3" key={anime.mal_id}>
                        <img className="anime-card-poster h-72" src={anime.images.jpg.image_url} alt={anime.title} />
                        <div className="anime-details p-3 w-full flex flex-col overflow-ellipsis overflow-auto">
                            <h3 className="anime-card-title text-xl tracking-wide text-balance text-baby-blue font-bold"> {anime.title}</h3>
                            <p className="anime-card-synopsis text-sm mt-2 text-gray-light"> Episodes: {anime.episodes || 'N/A'}</p>
                            <p className="anime-card-rating text-sm text-gray-light"> Rating: {anime.score}</p>
                            <p className="anime-card-status text-sm text-gray-light"> Status: {anime.status}</p>
                            <p className="anime-genres text-sm text-gray-light flex flex-wrap">
                                {anime.genres.map((genre) => (
                                    <p key={genre.mal_id} className="genre-btn px-2 bg-seafoam ms-1 rounded-lg mb-1 mt-2">{genre.name}</p>
                                ))}
                            </p>
                        </div>
                    </article>
                ))}
            </section>
        </>
    )
}

export default AnimeGallery;

Link to repo

For the duplicated titles I did try setting a key to equal the specific id of the show from the API but the duplicates are still present, and I’d also tried using a reduce method in place of my filter.

I also tried utilizing derived state and kept my state variable animeList as the only and entire list of the anime then one other state just for my search query so I could set the filter and specifications within my render, I didn’t notice any changes with that approach either, but to be fair, I had to do a lot of research on the concept of derived state so perhaps I didn’t implement it well.

2

Answers


  1. There is a duplicate mal_id in the API response, hence rendering a list with that as a key causes problems while updating the list.

    Try creating your own ids like below.

    useEffect(() => {
        const fetchAnimeGenre = async () => {
          const result = await fetch(animeApi);
          const data = await result.json();
          const transformedData = data.data.map((obj) => ({
            ...obj,
            id: crypto.randomUUID()
          }));
          setAnimeList(transformedData);
          setFilteredAnime(transformedData);
        };
        fetchAnimeGenre();
      }, []);
    

    Make sure you also use this new id as the key in the list. key={anime.id}

    Login or Signup to reply.
  2. Certainly! If you’d like to change the key in your map function to use the index instead of anime.mal_id, you can simply replace anime.mal_id with index in the key prop.

    Here’s the modified version of your component:

    import '../styles/AnimeGallery.module.css';
    
    const AnimeGallery = ({ animeList }) => {
        return (
            <>
                <section className="anime-gallery container mx-auto grid sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 px-3 py-3 mt-20">
                    {animeList.map((anime, index) => (
                        <article 
                            className="anime-card flex mx-3 my-2 rounded-lg h-72 bg-gray-dark shadow-md hover:-translate-y-3" 
                            key={index}  // Changed here
                        >
                            <img 
                                className="anime-card-poster h-72" 
                                src={anime.images.jpg.image_url} 
                                alt={anime.title} 
                            />
                            <div className="anime-details p-3 w-full flex flex-col overflow-ellipsis overflow-auto">
                                <h3 className="anime-card-title text-xl tracking-wide text-balance text-baby-blue font-bold">
                                    {anime.title}
                                </h3>
                                <p className="anime-card-synopsis text-sm mt-2 text-gray-light">
                                    Episodes: {anime.episodes || 'N/A'}
                                </p>
                                <p className="anime-card-rating text-sm text-gray-light">
                                    Rating: {anime.score}
                                </p>
                                <p className="anime-card-status text-sm text-gray-light">
                                    Status: {anime.status}
                                </p>
                                <p className="anime-genres text-sm text-gray-light flex flex-wrap">
                                    {anime.genres.map((genre) => (
                                        <p key={genre.mal_id} className="genre-btn px-2 bg-seafoam ms-1 rounded-lg mb-1 mt-2">
                                            {genre.name}
                                        </p>
                                    ))}
                                </p>
                            </div>
                        </article>
                    ))}
                </section>
            </>
        );
    }
    
    export default AnimeGallery;
    

    Explanation:

    • The key in the map method has been changed to index. The key should be a unique value to help React efficiently update and render components, but using index is okay when the list doesn’t change dynamically (e.g., items being added, removed, or reordered). However, if the list changes often, using a unique identifier like anime.mal_id is typically recommended to avoid potential rendering issues.

    Let me know if you need any further clarification!

    enter link description here

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search