Skip to content
This repository has been archived by the owner on Nov 2, 2024. It is now read-only.

Commit

Permalink
Fix typing, api error handling, and update tests
Browse files Browse the repository at this point in the history
  • Loading branch information
johnhunter committed Sep 30, 2023
1 parent 64c3895 commit 6ad63dc
Show file tree
Hide file tree
Showing 9 changed files with 62 additions and 25 deletions.
4 changes: 2 additions & 2 deletions src/App/components/Loading/Loading.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ describe('Loading', () => {
});

test('Displays given error', () => {
const message = 'given error';
const error = { name: 'HttpError', message: 'given error', status: 0 };
render(
<Loading status="error" error={{ message }}>
<Loading status="error" error={error}>
{children}
</Loading>,
);
Expand Down
4 changes: 2 additions & 2 deletions src/App/components/Loading/Loading.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { FC, ReactNode } from 'react';
import Info from '../Info';
import type { Status, Error } from '@/types';
import type { Status, HttpError } from '@/types';

interface LoadingProps {
status: Status;
error: Error | null;
error: HttpError | null;
children: ReactNode;
}

Expand Down
4 changes: 2 additions & 2 deletions src/App/components/Post/useData.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { useQuery } from '@tanstack/react-query';
import { getJson } from '@/api';
import type { PostData } from '@/types';
import type { PostData, HttpError } from '@/types';

const fetchPostById = async (id: number) =>
await getJson<PostData>(`posts/${id}`);

const useData = (postId: number) =>
useQuery<PostData, Error>(['post', postId], () => fetchPostById(postId), {
useQuery<PostData, HttpError>(['post', postId], () => fetchPostById(postId), {
enabled: !!postId,
});

Expand Down
3 changes: 2 additions & 1 deletion src/App/components/Posts/Posts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import css from './Posts.module.css';
import Info from '../Info';
import Loading from '../Loading';
import useData from './useData';
import { PostData } from '@/types';

interface PostsProps {
setPostId: (id: number | null) => void;
Expand All @@ -25,7 +26,7 @@ const Posts: FC<PostsProps> = ({ setPostId }) => {
Background Updating
</Info>
<ul className={css.list}>
{data.map(({ id, title }) => (
{data.map(({ id, title }: PostData) => (
<li key={id}>
<a
href="#"
Expand Down
4 changes: 2 additions & 2 deletions src/App/components/Posts/useData.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { useQuery } from '@tanstack/react-query';
import type { PostData } from '@/types';
import { getJson } from '@/api';
import { getJson, HttpError } from '@/api';

const useData = () =>
useQuery<PostData[], Error>(['posts'], async () =>
useQuery<PostData[], HttpError, PostData[]>(['posts'], async () =>
getJson<PostData[]>('posts'),
);

Expand Down
43 changes: 43 additions & 0 deletions src/api/HttpError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
const getErrorMessage = (response?: Response): string => {
const fallbackMsg = 'an unknown error';

if (!response) {
return fallbackMsg;
}

const code = typeof response.status === 'number' ? response.status : '';
const status = `${code} ${response.statusText || ''}`.trim();
return status ? `status code ${status}` : fallbackMsg;
};

interface ErrorOptions {
/**
* Expect cause to be an Error instance
*/
cause: Error;
}

/**
* HttpError provides a single error class to handle all failure responses
* from the fetch request.
*/
class HttpError extends Error {
public status: number | undefined;

constructor(response?: Response, options?: ErrorOptions) {
let errorMsg = 'an unknown error';

if (options?.cause.name === 'SyntaxError') {
errorMsg = 'an invalid json response';
} else {
errorMsg = getErrorMessage(response);
}

super(`Request failed with ${errorMsg}`, options);

this.name = 'HttpError';
this.status = response?.status;
}
}

export default HttpError;
17 changes: 6 additions & 11 deletions src/api/api.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import HttpError from './HttpError';

export const API_ROOT = 'https://jsonplaceholder.typicode.com/';
export type HttpError = Partial<Response> & {
status: number;
statusText: string;
};

type FetchArgs = Parameters<typeof globalThis.fetch>;

Expand All @@ -12,7 +10,7 @@ type FetchArgs = Parameters<typeof globalThis.fetch>;
export const fetch = async <RData = unknown>(
input: FetchArgs[0],
init: FetchArgs[1] = {},
) => {
): Promise<RData> => {
const initWithDefaults = {
...init,

Expand All @@ -26,18 +24,15 @@ export const fetch = async <RData = unknown>(
res = await globalThis.fetch(input, initWithDefaults);
resJson = (await res.json()) as RData;
} catch (err: unknown) {
const status = 0;
const statusText: string =
(err as Error)?.message || 'Failed to fetch (unknown error)';
return Promise.reject<HttpError>({ status, statusText });
throw new HttpError(undefined, { cause: err as Error });
}

if (res.ok) {
return resJson;
} else {
return Promise.reject<HttpError>(res);
throw new HttpError(res);
}
};

export const getJson = <RData = unknown>(path: string) =>
export const getJson = <RData = unknown>(path: string): Promise<RData> =>
fetch<RData>(`${API_ROOT}${path}`);
3 changes: 2 additions & 1 deletion src/api/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { API_ROOT, fetch, getJson, type HttpError } from './api';
export { API_ROOT, fetch, getJson } from './api';
export { default as HttpError } from './HttpError';
5 changes: 1 addition & 4 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { UseQueryResult } from '@tanstack/react-query';
export type { HttpError } from '@/api';

export type Status = UseQueryResult['status'];

Expand All @@ -7,7 +8,3 @@ export interface PostData {
title: string;
body: string;
}

export type Error = {
message: string;
};

0 comments on commit 6ad63dc

Please sign in to comment.