티스토리 뷰

💥 리뷰 목록 구현하기

1. BookReview.tsx

import { BookReviewItem as IBookReveiwItem } from "@/models/book.model"
import styled from "styled-components"
import BookReviewItem from "./BookReviewItem"

interface Props {
    reviews: IBookReveiwItem[]
}

function BookReview({ reviews }: Props) {
    return (
        <BookReviewStyle>
            {reviews.map((review) => (
                <BookReviewItem review={review} />
            ))}
        </BookReviewStyle>
    )
}

const BookReviewStyle = styled.div`
    { 생략 }
`

export default BookReview

 

2. BookReviewItem.tsx

import { 생략 }

interface Props {
    review: IBookReveiwItem
}

const Star = (props: Pick<IBookReveiwItem, "score">) => {
    return (
        <span className="star">
            {Array.from({ length: props.score }, (_, index) => (
                <FaStar />
            ))}
        </span>
    )
}

function BookReviewItem({ review }: Props) {
    return (
        <BookReviewItemStyle>
            <header className="header">
                <div>
                    <span>{review.userName}</span>
                    <Star score={review.score} />
                </div>
                <div>
                    {formatDate(review.created_at)}
                </div>
            </header>
            <div className="content">
                <p>{review.content}</p>
            </div>
        </BookReviewItemStyle>
        
    )
}


const BookReviewItemStyle = styled.div`
    { 생략 }
`

export default BookReviewItem

 

우선, BookReview.tsx 에서 리뷰들을 순회하면서 렌더링한다. 순회한 데이터들은 BookReviewItem.tsx 에서 뿌려지게 된다.

그리고 score 에 있는 숫자로 별 아이콘을 렌더링한다.

그럼 숫자만큼 별이 생성되고, 우리가 흔히 아는 리뷰에 있는 별점? 같은 느낌이 나게된다.

created_at 은 이전에 만든 formatDate 를 사용하여 년, 월, 일이 나오게 하였다.

 

💥 리뷰 목록 구현 - 결과물

 

그럼 이렇게 faker 로 만든 가짜 데이터로 리뷰 목록을 만들 수 있게 된다.

이름도 fakerKO 를 이용해 한국이름으로 나오게 만들었다. 글은 faker 에 있는 lorem 을 사용하였다.

 

💥 리뷰 작성 구현하기

1. BookReviewAdd.tsx

import { 생략 }

interface Props {
    onAdd: (data: BookReviewItemWrite) => void
}

function BookReviewAdd({ onAdd }: Props) {
    const { 
        register, 
        handleSubmit, 
        formState: { errors },
    } = useForm<BookReviewItemWrite>()

    return (
        <BookReviewAddStyle>
            <form onSubmit={handleSubmit(onAdd)}>
                <fieldset>
                    <textarea {...register("content", { required: true })}></textarea>
                    {errors.content && <p className="error-text">리뷰 내용을 입력해 주세요.</p>}
                </fieldset>
                <div className="submit">
                    <fieldset>
                        <select {...register("score", { required: true, valueAsNumber: true })}>
                            <option value="1">1점</option>
                            <option value="2">2점</option>
                            <option value="3">3점</option>
                            <option value="4">4점</option>
                            <option value="5">5점</option>
                        </select>
                    </fieldset>
                    <Button size="medium" scheme="primary">작성하기</Button>
                </div>
        </form>
        </BookReviewAddStyle>
    )
}

const BookReviewAddStyle = styled.div`
    { 생략 }
`

export default BookReviewAdd

 

2. BookReview.tsx

import { 생략 }

interface Props {
    reviews: IBookReveiwItem[]
    onAdd: (data: BookReviewItemWrite) => void
}

function BookReview({ reviews, onAdd }: Props) {
    return (
        <BookReviewStyle>
            <BookReviewAdd onAdd={onAdd} />
            {reviews.map((review) => (
                <BookReviewItem review={review} />
            ))}
        </BookReviewStyle>
    )
}

const BookReviewStyle = styled.div`
    { 생략 }
`

export default BookReview

 

3. review.ts

import { 생략 }

const mockReviewData: BookReviewItem[] = Array.from({ length: 8 }).map((_, index) => ({
    id: index,
    userName: faker.person.fullName(),
    content: faker.lorem.paragraph(),
    created_at: faker.date.past().toISOString(),
    score: faker.number.int({ min:1, max: 5})
}))

export const reviewsById = http.get("http://localhost:9999/reviews/:bookId", () => {
    return HttpResponse.json(mockReviewData, {
        status: 200
    })
})

export const addReview = http.post("http://localhost:9999/reviews/:bookId", () => {
    return HttpResponse.json(
        {
            message: "리뷰가 등록되었습니다."
        },
        {
            status: 200,
        }
    )
})

 

4. browser.ts

import { setupWorker } from "msw/browser"
import { addReview, reviewsById } from "./review"

const handlers = [reviewsById, addReview]

export const worker = setupWorker(...handlers)

 

5. review.api.ts

import { BookReviewItem, BookReviewItemWrite } from "@/models/book.model";
import { requestHandler } from "./http";

export const fetchBookReview = async (bookId: string) => {
    return await requestHandler<BookReviewItem>("get", `/reviews/${bookId}`)
}

interface AddBookReviewResponse {
    message: string
}

export const addBookReview = async (bookId: string, data: BookReviewItemWrite) => {
    return await requestHandler<AddBookReviewResponse>("post", `reviews/${bookId}`, )
}

 

6. BookDetail.tsx

 

BookReviewAdd.tsx 즉, 리뷰작성 컴포넌트 페이지를 만들었다.

도서 상세 페이지에서 아래의 리뷰 목록 위에 나타나게 하기 위해, BookDetail 파일에 있는 BookReview 컴포넌트에서

onAdd 도 추가해서 addReview 를 호출할 수 있도록 해주었다.

BookRevieweAdd 파일에 보면, select 부분에 있는 valueAsNumber 가 새롭게 배운 부분 이었는데,

select 같은 경우엔 클릭한 option 의 value 를 전달할 때, string 으로 전달이 된다.

하지만 score 를 위한 option 인 만큼 숫자가 전달되어야 하기 때문에, valueAsNumber 를 따로 적어주었다.

 

review.api 에는 addBookReview 가 추가되었고, 인터페이스로 AddBookReviewResponse 가 새로 생겼다.

이건 message 를 전달하기 위함이다.

review.ts 파일에서 addReview 부분을 보면, return 을 json 으로 response 하기로 했다.

그래서 message 와 status 가 전달되게 만들었는데, 이 부분을 위해 만들어진 인터페이스이다.

 

useBook.ts 에서 response 받은 message 를 알림창으로 띄워주기 위해 showAlert 을 했다.

이 부분에서 res 가 undefined 일 수도 있기 때문에, 인터페이스를 따로 지정해 준 것이다.

 

💥 리뷰 작성 구현 - 결과물

 

이렇게 내용을 적고, 아래에 있는 별점을 클릭한 후, 작성하기를 누르면 "리뷰가 등록되었습니다." 라는 알림창이 뜨게 된다.

select 는 default 값으로 1번째 의 값이 나타나있기 때문에, 값이 없을 이유가 없어서 error text 는 적지 않았다.

대신, 내용은 꼭 적어야 하는 부분이므로 errors 를 주고, error text 를 만들어 주었다.

그럼 내용이 없을 땐, "리뷰 내용을 입력해 주세요." 라는 빨간색 글씨가 뜨게 된다.

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/02   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28
글 보관함