From 397d08f30c72f635f98fd2baa0999c94ea6b83e9 Mon Sep 17 00:00:00 2001 From: iliasbo Date: Fri, 18 Sep 2026 17:42:16 +0200 Subject: [PATCH] fix: account for onlyFavorites filter in candidateIds (#37) --- src/App.tsx | 6 +-- src/components/PokemonList/PokemonList.tsx | 9 +++- src/controllers/pokemonController.ts | 50 +++++++++++++++------- src/controllers/useFavorites.ts | 1 + 4 files changed, 46 insertions(+), 20 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 8877cdb..acfcef3 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -13,7 +13,7 @@ import { GetNextFiltered, GetPrevFiltered } from "./controllers/pokemonControlle export default function App() { const [currentId, setCurrentId] = useState(1); - const { favoritePokemons, isFavorite, toggleFavorite, isLoadingFavorites } = + const { favoriteIds, favoritePokemons, isFavorite, toggleFavorite, isLoadingFavorites } = useFavorites(); const { filters, setFilter, resetFilters } = useFilters(); @@ -23,14 +23,14 @@ export default function App() { }); const handlePrevious = async () => { - const prevPokemon = await GetPrevFiltered(currentId, filters); + const prevPokemon = await GetPrevFiltered(currentId, filters, favoriteIds); if (prevPokemon) { setCurrentId(prevPokemon.id); } }; const handleNext = async () => { - const nextPokemon = await GetNextFiltered(currentId, filters); + const nextPokemon = await GetNextFiltered(currentId, filters, favoriteIds); if (nextPokemon) { setCurrentId(nextPokemon.id); } diff --git a/src/components/PokemonList/PokemonList.tsx b/src/components/PokemonList/PokemonList.tsx index 26b7e56..0665ee7 100644 --- a/src/components/PokemonList/PokemonList.tsx +++ b/src/components/PokemonList/PokemonList.tsx @@ -37,8 +37,13 @@ export function PokemonList({ const loading = !onlyFavorites && isPending; + // Extract IDs from favoritePokemons so matchesFilter can accurately check favorite status + const favoriteIds = favoritePokemons.map((pokemon) => pokemon.id); + const pokemonList = onlyFavorites - ? favoritePokemons.filter((pokemon) => matchesFilter(pokemon, filters)) + ? favoritePokemons.filter((pokemon) => + matchesFilter(pokemon, filters, favoriteIds), + ) : fetchedList; const ordered = [...pokemonList].sort((a, b) => @@ -139,4 +144,4 @@ async function buildPokemonList( } return pokemonList; -} +} \ No newline at end of file diff --git a/src/controllers/pokemonController.ts b/src/controllers/pokemonController.ts index 78b07ef..8b7134d 100644 --- a/src/controllers/pokemonController.ts +++ b/src/controllers/pokemonController.ts @@ -29,7 +29,10 @@ function isInRange(val: number, rangeStr?: string): boolean { export function matchesFilter( pokemon: PokemonData, rules: FilterRules, + favoriteIds: number[] = [], ): boolean { + + if (rules.onlyFavorites && !favoriteIds.includes(pokemon.id)) return false; if (rules.type && rules.type.length > 0) { const hasType = pokemon.types.some((t) => rules.type?.includes(t.type.name), @@ -92,15 +95,16 @@ export async function GetMany(ids: number[]): Promise { export async function GetPrevFiltered( currentId: number, rules: FilterRules, + favoriteIds: number[] = [], ): Promise { - const candidates = await candidateIds(rules); + const candidates = await candidateIds(rules, favoriteIds); if (candidates) { for (let i = candidates.length - 1; i >= 0; i--) { const id = candidates[i]; if (id >= currentId) continue; const pokemon = await fetchSafe(id); - if (pokemon && matchesFilter(pokemon, rules)) return pokemon; + if (pokemon && matchesFilter(pokemon, rules, favoriteIds)) return pokemon; } return null; } @@ -109,7 +113,7 @@ export async function GetPrevFiltered( while (prevId >= 1) { const pokemon = await fetchSafe(prevId); - if (pokemon && matchesFilter(pokemon, rules)) { + if (pokemon && matchesFilter(pokemon, rules, favoriteIds)) { return pokemon; } prevId--; @@ -120,14 +124,15 @@ export async function GetPrevFiltered( export async function GetNextFiltered( currentId: number, rules: FilterRules, + favoriteIds: number[] = [], ): Promise { - const candidates = await candidateIds(rules); + const candidates = await candidateIds(rules, favoriteIds); if (candidates) { for (const id of candidates) { if (id <= currentId) continue; const pokemon = await fetchSafe(id); - if (pokemon && matchesFilter(pokemon, rules)) return pokemon; + if (pokemon && matchesFilter(pokemon, rules, favoriteIds)) return pokemon; } return null; } @@ -136,7 +141,7 @@ export async function GetNextFiltered( while (nextId <= MAX_POKEMON_ID) { const pokemon = await fetchSafe(nextId); - if (pokemon && matchesFilter(pokemon, rules)) { + if (pokemon && matchesFilter(pokemon, rules, favoriteIds)) { return pokemon; } nextId++; @@ -144,21 +149,36 @@ export async function GetNextFiltered( return null; } -// The ids worth looking at for these rules, or null when every id qualifies. -// A type rule narrows it to the members of those types, which turns a walk -// through the whole pokedex into a walk through a short list +// Returns a sorted, deduplicated list of candidate IDs filtered by type and/or favorites, or null if no candidate filters are active. export async function candidateIds( rules: FilterRules, + favoriteIds: number[] = [], ): Promise { - if (!rules.type || rules.type.length === 0) { + const hasTypeFilter = Array.isArray(rules.type) && rules.type.length > 0; + + if (!rules.onlyFavorites && !hasTypeFilter) { return null; } - const lists = await Promise.all(rules.type.map(idsForType)); + let typeIdsSet: Set | null = null; + if (hasTypeFilter) { + const lists = await Promise.all(rules.type!.map(idsForType)); + // matchesFilter keeps a pokemon that has any of the chosen types, so the + // lists are merged rather than intersected + typeIdsSet = new Set(lists.flat()); + } + + if (rules.onlyFavorites) { + const uniqueFavorites = Array.from(new Set(favoriteIds)); - // matchesFilter keeps a pokemon that has any of the chosen types, so the - // lists are merged rather than intersected - return [...new Set(lists.flat())].sort((a, b) => a - b); + const result = typeIdsSet + ? uniqueFavorites.filter((id) => typeIdsSet!.has(id)) + : uniqueFavorites; + + return result.sort((a, b) => a - b); + } + + return Array.from(typeIdsSet!).sort((a, b) => a - b); } // The members of a type never change during a session, so the id list is @@ -181,4 +201,4 @@ async function idsForType(type: string): Promise { } catch { return []; } -} +} \ No newline at end of file diff --git a/src/controllers/useFavorites.ts b/src/controllers/useFavorites.ts index fa15a1a..c08c6ed 100644 --- a/src/controllers/useFavorites.ts +++ b/src/controllers/useFavorites.ts @@ -34,6 +34,7 @@ export function useFavorites() { ); return { + favoriteIds, favoritePokemons, isLoadingFavorites: favoriteIds.length > 0 && isPending, isFavorite,