From 7ded7332cc9fd05b2ee1e8c9610e719774c0910a Mon Sep 17 00:00:00 2001 From: Magnus Andreas Igesund Nerland Date: Wed, 16 Sep 2026 15:24:45 +0200 Subject: [PATCH 1/3] Set up TanStack Query provider --- package-lock.json | 27 +++++++++++++++++++++++++++ package.json | 1 + src/main.tsx | 7 ++++++- 3 files changed, 34 insertions(+), 1 deletion(-) 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/main.tsx b/src/main.tsx index df655ea..eb0dde4 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,10 +1,15 @@ import { StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import './index.css'; import App from './App.tsx'; +const queryClient = new QueryClient(); + createRoot(document.getElementById('root')!).render( - + + + ); From c94af21876032369f91f8218d96877fbddcc3a54 Mon Sep 17 00:00:00 2001 From: Magnus Andreas Igesund Nerland Date: Wed, 16 Sep 2026 15:49:43 +0200 Subject: [PATCH 2/3] Removed manual Pokemon list caching --- src/Api.tsx | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/src/Api.tsx b/src/Api.tsx index 939d521..51b7173 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 From af0cd48783731a5048a7b43f91e30b62ed79ce3b Mon Sep 17 00:00:00 2001 From: Magnus Andreas Igesund Nerland Date: Thu, 17 Sep 2026 11:01:40 +0200 Subject: [PATCH 3/3] Use TanStack Query for Pokemon data fetching --- src/Api.tsx | 17 --------------- src/App.tsx | 61 ++++++++++++++++++++++++++++++++++------------------- 2 files changed, 39 insertions(+), 39 deletions(-) diff --git a/src/Api.tsx b/src/Api.tsx index 51b7173..fe0b53f 100644 --- a/src/Api.tsx +++ b/src/Api.tsx @@ -51,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) => (