diff --git a/package-lock.json b/package-lock.json index 478fac6..17a41ab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "t18-project-1", "version": "0.0.0", "dependencies": { + "@tanstack/react-query": "^5.103.0", "pokenode-ts": "^2.3.1", "react": "^19.2.8", "react-dom": "^19.2.8" @@ -868,6 +869,32 @@ "dev": true, "license": "MIT" }, + "node_modules/@tanstack/query-core": { + "version": "5.103.0", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.103.0.tgz", + "integrity": "sha512-PfafnHQHEu7mZDsSalxSW/EBxPgF77k3atIJeQbqbb1Z7DmK2mH6YLJyqrvbbDk5M6Ua/egs7LLEs04a8robxA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.103.0", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.103.0.tgz", + "integrity": "sha512-Kn/cvTrNwFYpS/q1MYghMsVTSinmAKV1U2AnM3/x07PiczvC+nMoySqNpQK1jgDzVCOIHk5LlF2FhTlJKQmPAQ==", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "5.103.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", diff --git a/package.json b/package.json index 7e1c188..2ab744e 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "format:check": "prettier --check ." }, "dependencies": { + "@tanstack/react-query": "^5.103.0", "pokenode-ts": "^2.3.1", "react": "^19.2.8", "react-dom": "^19.2.8" diff --git a/src/Api.tsx b/src/Api.tsx index 939d521..fe0b53f 100644 --- a/src/Api.tsx +++ b/src/Api.tsx @@ -10,18 +10,13 @@ type PokemonListResponse = { results: Array<{ name: string; url: string }>; }; -// limit=100000 gir oss hele lista i ett kall. PokéAPI har ikke noe -// eget søke-endepunkt, så vi henter alle navnene én gang og filtrerer selv. +// PokéAPI har ikke eget søke-endepunkt, så vi henter Pokémon-lista +// og bruker den til lokal filtrering og søk const LIST_ENDPOINT = 'https://pokeapi.co/api/v2/pokemon?limit=10000'; -// Vi mellomlagrer lista i minnet så vi ikke henter den på nytt for hvert søk. -let cachedList: Array<{ name: string; url: string }> | null = null; - +// Henter den komplette Pokémon-lista fra PokéAPI +// Caching av serverdata håndteres av TanStack Query i React-laget async function fetchPokemonList() { - if (cachedList) { - return cachedList; - } - const response = await fetch(LIST_ENDPOINT); if (!response.ok) { @@ -29,9 +24,7 @@ async function fetchPokemonList() { } const data = (await response.json()) as PokemonListResponse; - cachedList = data.results; - console.log(cachedList); - return cachedList; + return data.results; } // i Api.tsx @@ -58,20 +51,3 @@ function idFromUrl(url: string): number { const match = url.match(/\/pokemon\/(\d+)\//); return match ? Number(match[1]) : 0; } - -export async function searchPokemon(query: string): Promise { - const list = await fetchPokemonList(); - const normalized = query.trim().toLowerCase(); - - return list - .filter((entry) => entry.name.includes(normalized)) - .map((entry) => ({ entry, id: idFromUrl(entry.url) })) - .filter(({ id }) => id > 0) - .slice(0, 24) - .map(({ entry, id }) => ({ - id, - name: entry.name, - imageUrl: `https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/${id}.png`, - apiUrl: `https://pokeapi.co/api/v2/pokemon/${id}`, - })); -} diff --git a/src/App.tsx b/src/App.tsx index daaa1e3..12c9a50 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,6 +1,7 @@ import { useState } from 'react'; import type { FormEvent } from 'react'; -import { searchPokemon } from './Api'; +import { useQuery } from '@tanstack/react-query'; +import { listAllPokemon } from './Api'; import type { Pokemon } from './Api'; import { useFavorites } from './useFavorites'; import FavoritesSidebar from './FavoritesSidebar'; @@ -11,12 +12,22 @@ function capitalize(text: string): string { } function App() { const [query, setQuery] = useState(''); + + //TanStack Query håndterer henting og caching av Pokémon-lista. + const { + data: allPokemon = [], + isPending, + isError, + error: queryError, + } = useQuery({ + queryKey: ['pokemon'], + queryFn: listAllPokemon, + }); const [pokemon, setPokemon] = useState([]); const [error, setError] = useState(''); - const [isLoading, setIsLoading] = useState(false); const { favorites, isFavorite, toggleFavorite } = useFavorites(); - async function handleSearch(event: FormEvent) { + function handleSearch(event: FormEvent) { event.preventDefault(); const trimmedQuery = query.trim(); @@ -26,22 +37,18 @@ function App() { return; } - setIsLoading(true); - setError(''); + const normalizedQuery = trimmedQuery.toLowerCase(); - try { - const results = await searchPokemon(trimmedQuery); - setPokemon(results); - if (results.length === 0) { - setError(`Fant ingen Pokémon som matcher «${trimmedQuery}».`); - } - } catch (searchError) { - setPokemon([]); - setError( - searchError instanceof Error ? searchError.message : 'Noe gikk galt.' - ); - } finally { - setIsLoading(false); + const results = allPokemon + .filter((entry) => entry.name.includes(normalizedQuery)) + .slice(0, 24); + + setPokemon(results); + + if (results.length === 0) { + setError(`Fant ingen Pokémon som matcher «${trimmedQuery}».`); + } else { + setError(''); } } @@ -63,17 +70,27 @@ function App() { placeholder="For eksempel: bulba" aria-label="Søk etter Pokémon" /> -
- {error &&

{error}

} - {!error && !isLoading && pokemon.length === 0 && ( + {isError && ( +

+ Kunne ikke hente Pokémon: {queryError.message} +

+ )} + + {!isError && error && ( +

{error}

+ )} + + {!isError && !error && !isPending && pokemon.length === 0 && (

Søkeresultatene dine vises her.

)} + {pokemon.map((entry) => (