From 907ac9426ed46d14f6e7a3cab29a2ce503e3fc37 Mon Sep 17 00:00:00 2001 From: Sawaymaan Singh Date: Mon, 14 Sep 2026 00:59:18 +0200 Subject: [PATCH] feat: add loading and error message components Add accessible Loading and ErrorMessage components, and expose refetch from useCountries for retrying failed requests. Closes #12 --- .../src/components/ErrorMessage.tsx | 27 +++++++++++++++++++ country-explorer/src/components/Loading.tsx | 19 +++++++++++++ country-explorer/src/hooks/useCountries.ts | 5 +++- 3 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 country-explorer/src/components/ErrorMessage.tsx create mode 100644 country-explorer/src/components/Loading.tsx diff --git a/country-explorer/src/components/ErrorMessage.tsx b/country-explorer/src/components/ErrorMessage.tsx new file mode 100644 index 0000000..c597af9 --- /dev/null +++ b/country-explorer/src/components/ErrorMessage.tsx @@ -0,0 +1,27 @@ +interface ErrorMessageProps { + /** User-friendly explanation of what went wrong (e.g. an ApiError message). */ + message: string; + /** Called when the user asks to try the request again. */ + onRetry: () => void; +} + +/** + * Generic error state for a failed API request. Rendered as an assertive + * live region so assistive technology announces the failure immediately, + * and always offers a way to retry instead of leaving the user stuck. + * + * Intended usage: pass `error instanceof ApiError ? error.message : '...'` + * from `useCountries` as `message`, and its `refetch` as `onRetry`. + */ +function ErrorMessage({ message, onRetry }: ErrorMessageProps) { + return ( +
+

{message}

+ +
+ ); +} + +export default ErrorMessage; diff --git a/country-explorer/src/components/Loading.tsx b/country-explorer/src/components/Loading.tsx new file mode 100644 index 0000000..3a853d6 --- /dev/null +++ b/country-explorer/src/components/Loading.tsx @@ -0,0 +1,19 @@ +interface LoadingProps { + /** Accessible status text; defaults to a generic loading message. */ + message?: string; +} + +/** + * Generic loading indicator for any async data fetch (e.g. `useCountries`). + * Rendered as a polite live region so assistive technology announces the + * status without interrupting whatever the user is doing. + */ +function Loading({ message = 'Loading…' }: LoadingProps) { + return ( +

+ {message} +

+ ); +} + +export default Loading; diff --git a/country-explorer/src/hooks/useCountries.ts b/country-explorer/src/hooks/useCountries.ts index c9adab9..0c39578 100644 --- a/country-explorer/src/hooks/useCountries.ts +++ b/country-explorer/src/hooks/useCountries.ts @@ -9,7 +9,7 @@ const DEFAULT_REGION = 'europe'; export const countriesQueryKey = (region: string) => ['countries', region] as const; export function useCountries(region: string = DEFAULT_REGION) { - const { data, isLoading, isError, error } = useQuery({ + const { data, isLoading, isError, error, refetch } = useQuery({ queryKey: countriesQueryKey(region), queryFn: () => getCountriesByRegion(region), }); @@ -19,5 +19,8 @@ export function useCountries(region: string = DEFAULT_REGION) { isLoading, isError, error, + // Exposed so a caller can wire it up as the retry action for an + // ErrorMessage component when a request fails. + refetch, }; }