Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
22 changes: 14 additions & 8 deletions src/components/DetailBtm.jsx → src/components/DetailBtm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,28 @@ import useApi from "./../hooks/useApi";
import ProfileBox from "./ProfileBox";
import CommentEmpty from "./../assets/comment_empty.png";
import useOutSideClick from "../hooks/useOutSideClick";
import Comment from "../types/Comment";

const DetailBtm = ({ id }) => {
const [commentMenu, setCommentMenu] = useState(null);
interface Props {
id?: string;
}

const DetailBtm = ({ id }: Props) => {
const [commentMenu, setCommentMenu] = useState<Props | null>(null);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

 const [commentMenu, setCommentMenu] = useState<string | null>(null);

이렇게 바꿔야하지 않을까요? 확신할 순 없는데
아래에서 해당 상태를 사용하는걸 보면 id값과 비교하는데 사용하네요

const [textValue, setTextValue] = useState("");

const handleTextChange = (e) => {
const handleTextChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
setTextValue(e.target.value);
};

const handleCommentMenu = (id) => {
const handleCommentMenu = (id: Props) => {
setCommentMenu((prev) => (prev === id ? null : id));
};

const menuRef = useRef(null);
useOutSideClick(menuRef, () => setCommentMenu(null));

const { data, loading, error } = useApi(`${id}/comments?limit=10`);
const { data, loading, error } = useApi<Comment>(`${id}/comments?limit=10`);

if (loading) {
return <div>Loading...</div>;
Expand Down Expand Up @@ -49,19 +54,20 @@ const DetailBtm = ({ id }) => {
</button>
</div>
<ul className="comment_list">
{data.list.length <= 0 ? (
{data?.list?.length === 0 ? (
<div className="comment_empty">
<div className="thum">
<img src={CommentEmpty} alt="아직 문의가 없어요 이미지" />
</div>
<p>아직 문의가 없어요</p>
</div>
) : (
data.list.map((el) => {
data?.list.map((el) => {
return (
<li key={el.id}>
<div className="comment_menu">
<button type="button" className="btn_reset" data-menu="button" onClick={() => handleCommentMenu(el.id)}>
<button type="button" className="btn_reset" data-menu="button" onClick={() => handleCommentMenu(el.id as any)}>
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

해당 버튼은 메뉴가 숨겨져있는 토글버튼인데,
아래 주석이 타입스크립트도 오류가 없고 깔끔해서 맞는거같은데 이상하게 토글버튼이 작동을 안합니다.
타입스크립트와 큰 관련이 없는거같은데 String이 이 아닌 as any 를 사용하면 작동을 합니다.
이유가 뭘까요??

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Props 타입은 id라는 key값을 가진 객체입니다

const handleCommentMenu = (id: Props) => {
    setCommentMenu((prev) => (prev === id ? null : id));
  };

이 함수부터가 잘못된거같은데요? 함수에서는 id파라미터를 마치 string타입처럼 사용하고있잖아요

const [commentMenu, setCommentMenu] = useState<Props | null>(null);

상태의 사용도 마찬가지구요 이건 다시한번 체크해보셔야할것 같네요

{/* 질문하기 <button type="button" className="btn_reset" data-menu="button" onClick={() => handleCommentMenu({ id: String(el.id) })}> */}
<img src={menuIcon} alt="댓글 옵션 열고닫기" />
</button>
{commentMenu === el.id && (
Expand Down
17 changes: 13 additions & 4 deletions src/components/DetailTop.jsx → src/components/DetailTop.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,18 @@ import productImg from "./../assets/item.png";
import wishIcon from "./../assets/icon_wish.svg";
import useApi from "./../hooks/useApi";
import ProfileBox from "./ProfileBox";
import Product from "../types/Product";

const DetailTop = ({ id }) => {
const { data, loading, error } = useApi(`${id}`);
interface ProductDetail extends Product {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extends 사용해보신것 좋습니다 ㅎㅎ

isFavorite: boolean;
}

interface Props {
id?: string;
}

const DetailTop = ({ id }: Props) => {
const { data, loading, error } = useApi<ProductDetail>(`${id}`);

if (loading) {
return <div>Loading...</div>;
Expand All @@ -22,7 +31,7 @@ const DetailTop = ({ id }) => {
return (
<div className="product_detail_item">
<div className="thum">
<img src={data.images && data.images[0] ? data.images[0] : productImg} alt={data.name} onError={(e) => (e.target.src = productImg)} />
<img src={data.images && data.images[0] ? data.images[0] : productImg} alt={data.name} onError={(e) => ((e.target as HTMLImageElement).src = productImg)} />
</div>
<div className="content">
<div className="tit">{data.name}</div>
Expand All @@ -44,7 +53,7 @@ const DetailTop = ({ id }) => {

<button className="btn_reset wish_area">
<div className="icon">
<img src={wishIcon} alt="" />
<img src={wishIcon} alt="좋아요 버튼" />
</div>
<div className="numb">{data.favoriteCount}</div>
</button>
Expand Down
10 changes: 8 additions & 2 deletions src/components/ProfileBox.jsx → src/components/ProfileBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,19 @@ import profileIcon from "./../assets/icon_profile.png";
import useDate from "../utils/formatDate";
import "./ProfileBox.css";

const ProfileBox = ({ img, name, date }) => {
interface Props {
img?: string;
name: string;
date: Date;
}

const ProfileBox = ({ img, name, date }: Props) => {
const formattedDate = useDate(date);

return (
<div className="name_area">
<div className="profile">
<img src={img ? img : profileIcon} alt={name} onError={(e) => (e.target.src = profileIcon)} />
<img src={img ? img : profileIcon} alt={name} onError={(e) => ((e.target as HTMLImageElement).src = profileIcon)} />
</div>
<div className="name">
<p>{name}</p>
Expand Down
13 changes: 6 additions & 7 deletions src/hooks/useApi.jsx → src/hooks/useApi.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,22 @@
import React, { useEffect, useState } from "react";
import axios from "axios";
import axios, { AxiosError } from "axios";

// axios 인스턴스 생성
const api = axios.create({
baseURL: "https://panda-market-api.vercel.app/products/",
});

const useApi = (url) => {
const [data, setData] = useState(null);
const useApi = <T,>(url: string) => {
const [data, setData] = useState<T | null>(null);

const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [error, setError] = useState<AxiosError | null>(null);
useEffect(() => {
const fetchProduct = async () => {
try {
const response = await api.get(url);
const response = await api.get<T>(url);
setData(response.data);
} catch (error) {
setError(error);
setError(error as AxiosError);
} finally {
setLoading(false);
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍👍👍👍

Expand Down
4 changes: 4 additions & 0 deletions src/page/ProductDetailPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ import DetailBtm from "../components/DetailBtm";
const ProductDetailPage = () => {
const { id } = useParams();

if (!id) {
return <div className="error-message">잘못된 접근입니다.</div>;
}

return (
<main className="main">
<div className="inner">
Expand Down
14 changes: 14 additions & 0 deletions src/types/Comment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export default interface Comment {
nextCursor: number;
list: {
writer: {
image: string;
nickname: string;
id: number;
};
updatedAt: Date;
createdAt: Date;
content: string;
id: number;
}[];
}
2 changes: 1 addition & 1 deletion src/types/Product.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export default interface Product {
createdAt: string;
createdAt: Date;
favoriteCount: number;
ownerNickname: string;
ownerId: number;
Expand Down
2 changes: 1 addition & 1 deletion src/utils/formatDate.js → src/utils/formatDate.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const useDate = (dateSet) => {
const useDate = (dateSet: Date) => {
const dateString = dateSet;
const date = new Date(dateString);
const year = date.getFullYear();
Expand Down