From 944ff91bd6685695353d47e6c70e409bdba20663 Mon Sep 17 00:00:00 2001 From: Sawaymaan Singh Date: Tue, 15 Sep 2026 02:16:25 +0200 Subject: [PATCH] feat: persist display choices in sessionStorage Add sessionStorage helpers and load/save the region filter and sort option on startup and on change. Closes #30 --- country-explorer/src/components/App.tsx | 31 +++++++++++++++++--- country-explorer/src/utils/sessionStorage.ts | 29 ++++++++++++++++++ country-explorer/src/utils/sortCountries.ts | 6 ++++ 3 files changed, 62 insertions(+), 4 deletions(-) create mode 100644 country-explorer/src/utils/sessionStorage.ts diff --git a/country-explorer/src/components/App.tsx b/country-explorer/src/components/App.tsx index 8bd2705..4b0a541 100644 --- a/country-explorer/src/components/App.tsx +++ b/country-explorer/src/components/App.tsx @@ -1,7 +1,8 @@ import './App.css'; -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import { useCountries } from '../hooks/useCountries'; -import { sortCountries, type SortOption } from '../utils/sortCountries'; +import { isSortOption, sortCountries, type SortOption } from '../utils/sortCountries'; +import { readSessionValue, writeSessionValue } from '../utils/sessionStorage'; import CountryCard from './CountryCard'; import CountrySelector from './CountrySelector'; import RegionFilter from './RegionFilter'; @@ -10,9 +11,17 @@ import Loading from './Loading'; import ErrorMessage from './ErrorMessage'; import NavigationControls from './NavigationControls'; +// sessionStorage keys for the display choices this component persists. +const REGION_STORAGE_KEY = 'selectedRegion'; +const SORT_STORAGE_KEY = 'sortOption'; + function App() { const { countries, isLoading, isError, error, refetch } = useCountries(); - const [selectedRegion, setSelectedRegion] = useState(''); + // Lazily read any saved choice on first render, so the page opens already + // showing what the user had before reloading within the same session. + const [selectedRegion, setSelectedRegion] = useState( + () => readSessionValue(REGION_STORAGE_KEY) ?? '', + ); // The fetched countries are European, so filter by their geographic subregion. const getRegion = (country: (typeof countries)[number]) => country.subregion?.trim() || country.region?.trim() || 'Unknown region'; @@ -24,7 +33,11 @@ function App() { const filteredCountries = selectedRegion ? countries.filter((country) => getRegion(country) === selectedRegion) : countries; - const [sortOption, setSortOption] = useState('name-asc'); + const [sortOption, setSortOption] = useState(() => { + const stored = readSessionValue(SORT_STORAGE_KEY); + // A missing or no-longer-valid stored value falls back to the default. + return isSortOption(stored) ? stored : 'name-asc'; + }); // A fresh, sorted copy — filteredCountries (and the cached query data it // derives from) is left untouched. const sortedCountries = sortCountries(filteredCountries, sortOption); @@ -36,6 +49,16 @@ function App() { (country) => country.code === activeCountryCode, ); + // Persist the display choices (never the fetched country/API data) so a + // reload within the same browser session restores them. + useEffect(() => { + writeSessionValue(REGION_STORAGE_KEY, selectedRegion); + }, [selectedRegion]); + + useEffect(() => { + writeSessionValue(SORT_STORAGE_KEY, sortOption); + }, [sortOption]); + function navigateCountry(direction: -1 | 1) { setSelectedCountryCode((currentCode) => { const currentIndex = sortedCountries.findIndex((country) => country.code === currentCode); diff --git a/country-explorer/src/utils/sessionStorage.ts b/country-explorer/src/utils/sessionStorage.ts new file mode 100644 index 0000000..fa187c2 --- /dev/null +++ b/country-explorer/src/utils/sessionStorage.ts @@ -0,0 +1,29 @@ +/** + * Thin, defensive wrapper around sessionStorage for small UI display + * choices (region filter, sort option) — never the fetched country data + * itself. Keys are namespaced so they don't collide with anything else on + * the page, and every call is wrapped in a try/catch since storage can be + * unavailable or full (e.g. private browsing, quota exceeded) — a failed + * read/write should never crash the app. + */ +const NAMESPACE = 'country-explorer'; + +function storageKey(key: string): string { + return `${NAMESPACE}:${key}`; +} + +export function readSessionValue(key: string): string | null { + try { + return window.sessionStorage.getItem(storageKey(key)); + } catch { + return null; + } +} + +export function writeSessionValue(key: string, value: string): void { + try { + window.sessionStorage.setItem(storageKey(key), value); + } catch { + // Storage unavailable or full — the choice just won't persist. + } +} diff --git a/country-explorer/src/utils/sortCountries.ts b/country-explorer/src/utils/sortCountries.ts index 336055c..c209966 100644 --- a/country-explorer/src/utils/sortCountries.ts +++ b/country-explorer/src/utils/sortCountries.ts @@ -9,6 +9,12 @@ export const SORT_OPTIONS: { value: SortOption; label: string }[] = [ { value: 'population-desc', label: 'Population (high to low)' }, ]; +// Used to validate a sort option loaded from sessionStorage — a stored +// value that isn't one of these is treated as missing/invalid. +export function isSortOption(value: string | null): value is SortOption { + return SORT_OPTIONS.some((option) => option.value === value); +} + /** * Returns a new, sorted array. The input list (React Query's cached data) * is never mutated — callers get a fresh copy to display.