Skip to content

Commit

Permalink
feat: server action의 에러 status code를 클라이언트에 전달
Browse files Browse the repository at this point in the history
  • Loading branch information
yeolyi committed Apr 6, 2024
1 parent 6aff3f8 commit e9883dc
Show file tree
Hide file tree
Showing 7 changed files with 46 additions and 21 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/deploy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ on:
push:
branches:
- main
- qa
- qa2

jobs:
build:
Expand Down
19 changes: 19 additions & 0 deletions actions/errorHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// https://github.com/vercel/next.js/discussions/49506

'use server';

import { isDynamicUsageError } from 'next/dist/export/helpers/is-dynamic-usage-error';

import { CustomError } from '@/utils/serverActionError';

export const withErrorHandler = <T extends Array<unknown>, U>(fn: (...args: T) => Promise<U>) => {
return async (...args: T): Promise<U | CustomError> => {
try {
return await fn(...args);
} catch (e) {
if (isDynamicUsageError(e)) throw e;

return { error: { message: (e as Error).message } };
}
};
};
5 changes: 3 additions & 2 deletions actions/reservation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,19 @@ import { FETCH_TAG_RESERVATION } from '@/constants/network';

import { Reservation, ReservationPostBody, ReservationPreview } from '@/types/reservation';

import { withErrorHandler } from './errorHandler';
import { deleteRequest, getRequest, postRequest } from '../apis';

const reservationPath = '/reservation';

export const postReservation = async (body: ReservationPostBody) => {
export const postReservation = withErrorHandler(async (body: ReservationPostBody) => {
await postRequest(reservationPath, {
body: JSON.stringify(body),
headers: { 'Content-Type': 'application/json' },
jsessionID: true,
});
revalidateTag(FETCH_TAG_RESERVATION);
};
});

export const getWeeklyReservation = async (params: {
roomId: number;
Expand Down
11 changes: 2 additions & 9 deletions apis/common.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,7 @@
export const BASE_URL = 'https://cse-dev-waffle.bacchus.io/api/v1';

export class NetworkError extends Error {
statusCode: number;
constructor(statusCode: number) {
super(`${statusCode} 에러`);
this.statusCode = statusCode;
}
}

export const checkError = (response: Response) => {
if (response.ok) return;
throw new NetworkError(response.status);
// server action 에러 처리를 위해 status code만 깔끔하게 담음
throw new Error(response.status.toString());
};
3 changes: 1 addition & 2 deletions apis/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { cookies } from 'next/headers';

import { objToQueryString } from '@/utils/convertParams';

import { BASE_URL, NetworkError, checkError } from './common';
import { BASE_URL, checkError } from './common';

type CredentialRequestInit = RequestInit & { jsessionID?: boolean };

Expand Down Expand Up @@ -53,7 +53,6 @@ const fetchWithRetry = async (
try {
return await _fetch(url, method, init);
} catch (e) {
if (e instanceof NetworkError) throw e;
if (remain === 0) throw e;

console.error(`fetchWithRetry: ${e} ${url} ${method} ${init}`);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@ import { useState, FormEventHandler } from 'react';

import { postReservation } from '@/actions/reservation';

import { NetworkError } from '@/apis/common';

import { ReservationPostBody } from '@/types/reservation';

import { isSameDay } from '@/utils/date';
import { refreshPage } from '@/utils/refreshPage';
import { handleServerAction } from '@/utils/serverActionError';
import { infoToast, errorToast } from '@/utils/toast';

import getOptimalEndTime from './getOptimalEndTime';
Expand All @@ -30,7 +29,7 @@ export default function useAddReservation(roomId: number) {
}

try {
await postReservation(body);
handleServerAction(await postReservation(body));
// TODO: revalidate같은거 써서 좀 더 예쁘게
refreshPage();
} catch (e) {
Expand Down Expand Up @@ -103,14 +102,12 @@ const checkSubmit = (body: ReservationPostBody) => {
};

const handleError = (e: unknown) => {
if (e instanceof NetworkError) {
if (e.statusCode === 409) {
if (e instanceof Error) {
if (e.message === '409') {
errorToast('해당 위치에 이미 예약이 존재합니다.');
} else {
errorToast(e.message);
}
} else if (e instanceof Error) {
errorToast(e.message);
} else {
errorToast('알 수 없는 에러');
}
Expand Down
16 changes: 16 additions & 0 deletions utils/serverActionError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export type CustomError = { error: { message: string } };

export const isApiError = <T>(response: T | CustomError): response is CustomError => {
return typeof response === 'object' && response !== null && 'error' in response;
};

export function throwIfError<T>(response: T): asserts response is Exclude<T, CustomError> {
if (isApiError(response)) {
throw new Error(response.error.message);
}
}

export function handleServerAction<T>(response: T): Exclude<T, CustomError> {
throwIfError(response);
return response;
}

0 comments on commit e9883dc

Please sign in to comment.