export default function Page() {
return (
<h1>서버 액션을 통해 리뷰 추가/조회 기능 구현하기 🤓</h1>
);
}
required
키워드를 통해, 사용자의 입력을 강제할 수 있다.➡️ 클라이언트 측에서의 예외처리
if (!content || !author) {
return;
}
➡️ 서버 측에서의 예외처리
먼저 리뷰 생성 API의 형태는 위와 같다. 이를 참고하며 다음과 같이 작성된 코드를 살펴보자.
try {
// api 경로, fetch 요청의 옵션 객체
const response = await fetch(
`${process.env.NEXT_PUBLIC_API_SERVER_URL}/review`, {
method: "POST",
body: JSON.stringify({ bookId, content, author }),
}
);
console.log(response.status);
} catch(err) {
console.error(err);
return;
}
위 코드는 리뷰 생성을 위해, 지난 포스트에서 작성한 서버 액션 함수에 추가한 코드이다.
fetch 메서드의 첫 번째 인수로 api 경로를 넣어주는 것은 기존과 같으며, 메서드의 두 번째 매개변수로는 요청의 옵션 객체를 넣어준다. 이 때 API 형태를 참고하여 작성한다.
bookId 값은 서버 액션 함수 내에 존재하지 않으므로, Page 컴포넌트에서 props로 넘겨 받아 넣어준다. content, author 값은 서버 액션을 통해 받은 데이터에서 get 메서드를 통해 꺼내 온 값이다.
리뷰 조회 API의 형태는 위와 같다. 이를 참고하여 지금까지 해온 방식으로 fetch 메서드를 사용해 리뷰 조회 함수를 구현하면 된다.
async function ReviewList({ bookId }: { bookId: string }) {
const response = await fetch(
`${process.env.NEXT_PUBLIC_API_SERVER_URL}/review/book/${bookId}`
);
// 예외 처리
if (!response.ok) {
throw new Error(`Review fetch failed: ${response.statusText}`);
}
const reviews = await response.json();
return (
<section></section>
);
}
먼저 우리 프로젝트는 📁 error.tsx 파일을 통해 예외를 처리하도록 적용해놓았으므로, 에러 발생 시 Error 객체를 던질 수 있도록 처리하였다.
또한 API를 받아와 json화한 reviews
객체의 타입이 any로 추론되고 있는 상태이므로, 이에 대한 타입을 구체화하기 위해 따로 타입을 정의하는 것이 필요하다고 판단하였다.
export interface ReviewData {
id: number;
content: string;
author: string;
createdAt: string;
bookId: number;
}
받아 오는 response의 형태를 참고하여 위와 같이 데이터의 타입을 정의하였다.
const reviews: ReviewData [] = await response.json();
따라서 정의한 새로운 타입을 이용하여, 위와 같이 reviews
객체의 타입을 ReviewData의 배열 형태로 선언해줄 수 있다.
import { ReviewData } from '@/types';
import style from './review-item.module.css';
export default function ReviewItem({
id,
content,
author,
createdAt,
bookId
}: ReviewData) {
return (
<div>
<div>{author}</div>
<div>{content}</div>
<div>
<div>{new Date(createdAt).toLocaleString()}</div>
<div>삭제하기</div>
</div>
</div>
);
}
하나의 리뷰를 띄우기 위한 컴포넌트를 ReviewItem이라는 이름으로 구현하였다. 스타일이 적용되지 않은 초기 상태이다.
'삭제하기'는 후에 버튼으로 변경할 예정이다.
return (
<section>
{reviews.map((review) => (
<ReviewItem key={`review-item-${review.id}`} {...review} />
))}
</section>
);
데이터 페칭 후 저장한 reviews 배열을 위에서 선언한 ReviewItem 컴포넌트를 이용해서 화면에 뿌려주면 된다.