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
27 changes: 27 additions & 0 deletions country-explorer/src/components/ErrorMessage.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div role="alert" aria-live="assertive">
<p>{message}</p>
<button type="button" onClick={onRetry}>
Try again
</button>
</div>
);
}

export default ErrorMessage;
19 changes: 19 additions & 0 deletions country-explorer/src/components/Loading.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<p role="status" aria-live="polite">
{message}
</p>
);
}

export default Loading;
5 changes: 4 additions & 1 deletion country-explorer/src/hooks/useCountries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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),
});
Expand All @@ -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,
};
}