Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
179 changes: 124 additions & 55 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,21 +172,68 @@ API-et, for eksempel hovedstad eller areal, og er derfor valgfrie i typen.
cachen ikke nullstilles når komponenter rendres på nytt.
- `useCountries` bruker den faste query key-en `['countries']`. Alle
komponenter som bruker hooken, deler dermed samme data og forespørsel.
- `fetchCountries` sjekker `response.ok` og kaster en feil ved
HTTP-feil, for eksempel 429 eller 500. TanStack Query sender med et
`AbortSignal`, som sendes videre til `fetch`.
- TanStack Query sender med et `AbortSignal`, som `fetchCountries`
sender videre til `fetch`.
- `staleTime` er én time. Landdata endres sjelden, og API-et oppgir
selv `Cache-Control: max-age=3600`. Så lenge dataene er ferske, gir
nye rendringer og nye komponenter ingen nye kall.
- `refetchOnWindowFocus: false` gjør at fanebytte ikke gir nye kall.
- `retry: 1` gir ett automatisk nytt forsøk. Deretter vises en
feilmelding med knappen «Prøv igjen», som kaller `refetch`.
- `refetchOnReconnect` og `refetchOnMount` har standardverdien `true`,
men gjelder bare data som er eldre enn `staleTime`. Kommer nettet
tilbake etter mer enn én time, hentes dataene på nytt. Ellers skjer
ingenting. `App` monteres bare én gang, så `refetchOnMount` gir ingen
ekstra kall.
- `retry: 1` gir ett automatisk nytt forsøk, også når brukeren trykker
«Prøv igjen». Deretter vises feilmeldingen. Flere forsøk ville bare
gjort feilmeldingen tregere, og brukeren kan prøve igjen selv.
- `gcTime` har standardverdien fem minutter. Den har ingen praktisk
betydning her, fordi `App` alltid bruker queryen og dataene derfor
aldri fjernes fra cachen mens appen er åpen.
- Cachen ligger bare i minnet og forsvinner ved reload. Da hentes
dataene på nytt, men nettleseren kan bruke sin egen HTTP-cache.

React Strict Mode er beholdt. I utviklingsmodus monterer Strict Mode
komponentene to ganger. Nettverksfanen kan derfor vise én avbrutt og én
fullført forespørsel. I produksjonsbygget gjøres én forespørsel.
- Sortering, favoritter, favorittfilter, blaing og valg av land skjer
lokalt på dataene som allerede er hentet. De endrer ikke query key-en
og gir derfor ingen nye kall.

### Feilhåndtering

`fetchCountries` gjør feil om til norske meldinger. `App` viser
«Kunne ikke hente landdata.» etterfulgt av meldingen, og en knapp
«Prøv igjen» som kaller `refetch`.

| Situasjon | Melding |
| ------------------------------------------ | ------------------------------------------------------- |
| Ingen kontakt (nettverks- eller CORS-feil) | «Fikk ikke kontakt med API-et. Sjekk nettforbindelsen.» |
| HTTP-feil, for eksempel 429 eller 500 | «API-et svarte med en feil (HTTP 500).» |
| Svaret er ikke gyldig JSON | «Svaret fra API-et var ikke gyldig JSON.» |
| Svaret er ikke en array | «Svaret fra API-et hadde et uventet format.» |

- Den opprinnelige feilen legges ved som `cause`, slik at den kan
undersøkes ved feilsøking.
- En forespørsel som avbrytes via `AbortSignal`, sendes videre uendret.
TanStack Query håndterer den selv, og den vises ikke som feil.
- **Tomt svar:** `[]` gir «Fant ingen land.».
- **Manglende felter:** Land uten kode eller navn hoppes over. Andre
felter som mangler eller har feil type, blir `undefined`, og kortet
viser «Ikke oppgitt».
- **Feil etter vellykket henting:** Feiler en senere henting i
bakgrunnen, for eksempel ved gjenoppkobling, beholder TanStack Query
de gamle dataene. `App` viser derfor feilsiden bare når det ikke finnes
data, slik at brukeren kan fortsette med landene som allerede er hentet.

### Utviklingsmodus og produksjonsbygg

React Strict Mode er beholdt. I utviklingsmodus (`npm run dev`) monterer
Strict Mode komponentene to ganger. Første forespørsel avbrytes da, og en
ny startes. Nettverksfanen viser derfor én avbrutt og én fullført
forespørsel ved oppstart. Dette er forventet og skjer bare i utvikling.
Produksjonsbygget (`npm run build` og `npm run preview`) gjør én
forespørsel. Flere kall i produksjonsbygget ville vært et problem.

### API-nøkler

countries.dev krever ingen API-nøkkel. Repoet og klientkoden inneholder
ingen nøkler, tokens eller `.env`-filer.

### Landkort

Expand Down Expand Up @@ -504,11 +551,59 @@ Kontrollene ble kjørt i utviklingsserveren (Vite) i nettleseren:
- Sidemenyen: valg av land, markering av valgt land og favoritter,
oppsett på smal skjerm og bruk med tastatur.

### Kontroller av API-kall og feiltilstander

Kontrollene ble kjørt i headless Chrome med Chrome DevTools Protocol mot
produksjonsbygget (`npm run preview`) og utviklingsserveren
(`npm run dev`). API-kall ble telt i nettverkshendelsene. Feilsvar ble
simulert ved å avskjære forespørselen til countries.dev. Skriptene er
ikke lagt i repoet.

- `npm run lint`, `npm run build` og `prettier --check` for de endrede
filene besto.
- **Ingen nøkler:** Søk i `src`, `index.html`, `vite.config.ts` og
sporede filer ga ingen API-nøkler, tokens eller `.env`-filer.
- **Nettverkskall i produksjonsbygget:** Totalt 1 kall til
`countries.dev/countries` gjennom hele økten. Tallet var uendret etter
hvert av disse stegene:
- Blaing med Neste, Neste og Forrige.
- Direktevalg i menyen.
- Sortering etter befolkning og tilbake til navn.
- Favorittendring for to land og favorittfilter av og på.
- Rerender via endret vindusstørrelse, og fokus- og
`visibilitychange`-hendelser.
- Nettet av og på igjen mens dataene var ferske.

Etter reload ble det gjort 1 nytt kall, som forventet.

- **Utviklingsserveren:** Samme steg ga 1 avbrutt og 1 fullført kall ved
oppstart, og ingen flere. Dette er Strict Mode, se
[Utviklingsmodus og produksjonsbygg](#utviklingsmodus-og-produksjonsbygg).
- **Feiltilstander (produksjonsbygg):**
- HTTP 500, HTTP 429, ugyldig JSON, et objekt i stedet for array og
nettverksfeil ga hver sin melding fra tabellen under
[Feilhåndtering](#feilhåndtering), med knappen «Prøv igjen». Hvert
tilfelle ga 2 kall (første forsøk og ett nytt forsøk).
- `[]` ga «Fant ingen land.».
- Land med manglende felter og felter med feil type ga ingen krasj.
Land uten kode ble hoppet over («Land 1 av 2»).
- **«Prøv igjen»:** Etter nettverksfeil viste klikket «Laster land …» og
deretter landene fra det ekte API-et. Når nytt forsøk feilet igjen
(HTTP 503), ga klikket 2 nye kall, og feilmeldingen og knappen kom
tilbake.
- **Feil etter vellykket henting:** Kontrollert med `@tanstack/query-core`
i Node. Etter en vellykket henting og en feilet ny henting var
`isError` `true`, mens dataene fortsatt fantes. Før denne endringen
ville `App` derfor vist feilsiden. Nå vises dataene videre.
- Ingen JavaScript-feil eller konsollfeil i noen av kjøringene.

Automatiske tester som gjenstår når Vitest er satt opp:

- `fetchCountries`: vellykket henting, HTTP-feil, uventet format, tom
respons og manglende felter, med mocket `fetch`.
- `App`: lastetilstand, feilmelding, «Prøv igjen» og tomt resultat.
- `fetchCountries`: vellykket henting, HTTP-feil, nettverksfeil, ugyldig
JSON, uventet format, tom respons, manglende felter og avbrutt
forespørsel, med mocket `fetch`.
- `App`: lastetilstand, feilmelding med riktig tekst, «Prøv igjen», tomt
resultat, og at eksisterende data vises når en ny henting feiler.
- `CountryCard`: fullstendige data, manglende felter og flagg som ikke
kan lastes.
- `CountryNavigation`: forrige/neste gir riktig land, knappene er
Expand Down Expand Up @@ -542,46 +637,20 @@ Automatiske tester som gjenstår når Vitest er satt opp:
ChatGPT ble brukt som støtte til prosjektplanlegging og veiledning
for oppsett av Vite, React, TypeScript, ESLint og Prettier.

Claude Code (Anthropic) ble brukt til API-integrasjonen med TanStack
Query. Claude Code gjorde dette:

- Undersøkte dokumentasjonen og faktiske svar fra countries.dev.
- Foreslo datautvalget og installerte `@tanstack/react-query`.
- Skrev `src/types/country.ts`, `src/api/countries.ts`,
`src/hooks/useCountries.ts` og `src/components/CountryList.tsx`.
- Endret `src/main.tsx`, `src/App.tsx` og `src/App.css`.
- Kjørte kontrollene beskrevet under
[Kontroller av API-integrasjonen](#kontroller-av-api-integrasjonen).
- Skrev README-avsnittene om API-integrasjonen.

Claude Code ble også brukt til landkortet (issue #7). Claude Code
skrev `src/components/CountryCard.tsx` og `CountryCard.css`, flyttet
lasting og feilhåndtering fra `CountryList` til `src/App.tsx`, fjernet
`CountryList.tsx`, kjørte kontrollene under
[Kontroller av landkortet](#kontroller-av-landkortet) og oppdaterte
README.

Claude Code ble også brukt til navigasjonen (issue #8). Claude Code
skrev `src/components/CountryNavigation.tsx` og `CountryNavigation.css`,
la til valgt landkode som state i `src/App.tsx`, kjørte lint, formatering
og bygg, og oppdaterte README.

Claude Code ble også brukt til sorteringen (issue #9). Claude Code
skrev `src/utils/sortCountries.ts`, `src/utils/sortOrderStorage.ts`,
`src/hooks/useSortOrder.ts`, `src/components/SortControl.tsx` og
`SortControl.css`, koblet sorteringen inn i `src/App.tsx`, kjørte
kontrollene under
[Kontroller av sorteringen](#kontroller-av-sorteringen) og oppdaterte
README.

Claude Code ble også brukt til favorittene (issue #10). Claude Code
skrev `src/utils/favoritesStorage.ts`, `src/hooks/useFavorites.ts`,
`src/components/FavoriteButton.tsx` og `FavoriteButton.css`, la til
plass for knappen i `CountryCard`, koblet favorittene inn i
`src/App.tsx`, kjørte kontrollene under
[Kontroller av favorittene](#kontroller-av-favorittene) og oppdaterte
README.

Endringene skal gjennomgås av gruppen i pull requesten før de merges.
Dokumentasjonen oppdateres med videre KI-bruk og hvordan forslag er
kontrollert av gruppen.
Claude Code (Anthropic) ble brukt gjennom prosjektet, i tre roller:

- **Kodeutkast:** Claude Code laget førsteutkast til kode for
issues: landkortet (#7), favorittene (#10), favorittfilteret og sidemenyen
(#11) og feilhåndteringen (#16). Gruppen bestemte hva som skulle
lages, gjennomgikk utkastene i pull requests og endret der løsningen ikke passet. Et eksempel er at landvalget ble flyttet fra en
nedtrekksliste til en sidemeny.
- **Sparringspartner:** Vi brukte Claude Code til å diskutere
løsningsvalg, for eksempel valg av API etter at REST Countries krevde
autentisering, cache- og refetch-innstillinger i TanStack Query, og
hvordan valgt land skal oppføre seg ved sortering og filtrering.
- **Kontroll og dokumentasjon:** Claude Code verifiserte API-svar,
kjørte lint, formatering og bygg, kontrollerte nettverkskall og
feiltilstander i nettleseren, og hjalp med å skrive README.

Alle endringer er gjennomgått av et annet gruppemedlem i pull request
før merge.
9 changes: 6 additions & 3 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { sortCountries, type SortOrder } from './utils/sortCountries';
import './App.css';

function App() {
const { data, isPending, isError, refetch } = useCountries();
const { data, error, isPending, refetch } = useCountries();
// Landkoden til valgt land. null betyr at brukeren ikke har valgt noe ennå.
const [selectedCode, setSelectedCode] = useState<string | null>(null);
// Valgt sortering, lagret i sessionStorage.
Expand Down Expand Up @@ -62,10 +62,13 @@ function App() {
// Ved «Prøv igjen» går queryen tilbake til pending, så lastetilstanden vises igjen.
if (isPending) {
content = <p role="status">Laster land …</p>;
} else if (isError) {
} else if (!data) {
// Feilsiden vises bare når det ikke finnes data. Feiler en senere henting
// i bakgrunnen, beholder TanStack Query de gamle dataene, og appen
// fortsetter å vise dem i stedet for å bytte til feilsiden.
content = (
<div role="alert" className="country-error">
<p>Kunne ikke hente landdata. Sjekk nettforbindelsen og prøv igjen.</p>
<p>Kunne ikke hente landdata. {error?.message}</p>
<button type="button" onClick={() => refetch()}>
Prøv igjen
</button>
Expand Down
32 changes: 29 additions & 3 deletions src/api/countries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,40 @@ const params = new URLSearchParams({
limit: String(COUNTRY_LIMIT),
});

// Feilmeldingene vises for brukeren, så de er skrevet på norsk.
export async function fetchCountries(signal?: AbortSignal): Promise<Country[]> {
const response = await fetch(`${API_URL}?${params.toString()}`, { signal });
let response: Response;

try {
response = await fetch(`${API_URL}?${params.toString()}`, { signal });
} catch (error) {
// En avbrutt forespørsel er ikke en feil for brukeren. TanStack Query
// håndterer den selv, så den sendes videre uendret.
if (signal?.aborted) {
throw error;
}
// Den opprinnelige feilen legges ved som cause, nyttig ved feilsøking.
throw new Error('Fikk ikke kontakt med API-et. Sjekk nettforbindelsen.', {
cause: error,
});
}

if (!response.ok) {
throw new Error(`Serveren svarte med HTTP ${response.status}.`);
throw new Error(`API-et svarte med en feil (HTTP ${response.status}).`);
}

const data: unknown = await response.json();
let data: unknown;

try {
data = await response.json();
} catch (error) {
if (signal?.aborted) {
throw error;
}
throw new Error('Svaret fra API-et var ikke gyldig JSON.', {
cause: error,
});
}

if (!Array.isArray(data)) {
throw new Error('Svaret fra API-et hadde et uventet format.');
Expand Down
2 changes: 2 additions & 0 deletions src/hooks/useCountries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ export function useCountries() {
staleTime: 60 * 60 * 1000,
// Unngå ny henting bare fordi brukeren bytter fane eller vindu.
refetchOnWindowFocus: false,
// refetchOnReconnect beholder standardverdien: når nettet kommer tilbake,
// hentes dataene på nytt bare hvis de er eldre enn staleTime.
// Ett automatisk nytt forsøk. Deretter vises feilmeldingen,
// og brukeren kan prøve igjen selv.
retry: 1,
Expand Down