-
Notifications
You must be signed in to change notification settings - Fork 48
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
경북대 FE_이정민_3주차 과제 Step2 #57
base: userjmmm
Are you sure you want to change the base?
Changes from all commits
9de3360
4817cc4
f6df8eb
1f441f6
fb9b0d5
5c4521d
6b08761
923c885
402dba8
98221da
b12d2cd
bb2665e
f638bf9
08ecc59
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
## 2단계 - Error, Loading Status 핸들링 하기 | ||
|
||
### 기능 구현 목록 | ||
- 각 API에서 Loading 상태에 대한 UI 대응 | ||
- [x] Loading 상태를 보여주는 UI component 만들기 | ||
- [x] 1단계에서 사용 중인 API에 적용하기 | ||
|
||
- [x] 데이터가 없는 경우에 대한 UI component 만들기 | ||
- [x] Http Status에 따라 Error UI component 만들기 | ||
- [x] 1단계에서 사용 중인 API에 적용하기 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import axios from 'axios'; | ||
|
||
const Api = axios.create({ | ||
baseURL: 'https://react-gift-mock-api-userjmmm.vercel.app/', | ||
}); | ||
|
||
export const fetchData = async (endpoint: string, params = {}) => { | ||
try { | ||
const response = await Api.get(endpoint, { params }); | ||
return response.data; | ||
} catch (error) { | ||
if (axios.isAxiosError(error)) { // axios는 자동으로 JSON 변환해줌 | ||
const code = error.response?.status?.toString() || 'UNKNOWN_ERROR'; | ||
const description = error.response?.data?.description || '알 수 없는 오류가 발생했어요.'; | ||
throw Object.assign(new Error(), {code, description }); | ||
} | ||
} | ||
}; | ||
|
||
export default Api; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import styled from "@emotion/styled"; | ||
|
||
interface EmptyDataProps { | ||
message?: string; | ||
} | ||
|
||
const EmptyData = ({ message = "보여줄 데이터가 없어요 🤨" }: EmptyDataProps) => { | ||
return ( | ||
<EmptyDataContainer> | ||
{message} | ||
</EmptyDataContainer> | ||
) | ||
}; | ||
|
||
const EmptyDataContainer = styled.div` | ||
display: flex; | ||
justify-content: center; | ||
align-items: center; | ||
height: 100%; | ||
width: 100%; | ||
color: #555; | ||
font-size: 18px; | ||
text-align: center; | ||
padding: 20px; | ||
`; | ||
|
||
export default EmptyData; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import styled from "@emotion/styled"; | ||
|
||
interface ErrorMessageProps { | ||
code?: string; | ||
message: string; | ||
} | ||
|
||
const ErrorMessage = ({ code, message }: ErrorMessageProps) => { | ||
return ( | ||
<ErrorContainer> | ||
{code && <Code>(Error Code: {code}) </Code>} {message} <Emoji>😔</Emoji> | ||
</ErrorContainer> | ||
) | ||
}; | ||
|
||
const ErrorContainer = styled.div` | ||
display: flex; | ||
justify-content: center; | ||
align-items: center; | ||
height: 100%; | ||
width: 100%; | ||
font-size: 18px; | ||
text-align: center; | ||
padding: 20px; | ||
`; | ||
|
||
const Code = styled.span` | ||
margin-right: 10px; | ||
font-size: 16px; | ||
color: #d32f2f; | ||
`; | ||
|
||
const Emoji = styled.span` | ||
margin-left: 5px; | ||
`; | ||
|
||
export default ErrorMessage; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import { keyframes } from '@emotion/react'; | ||
import styled from '@emotion/styled'; | ||
|
||
const Loading: React.FC = () => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 여기서는 왜 React.FC를 사용하셨나요? |
||
return ( | ||
<LoadingContainer> | ||
<Spinner /> | ||
</LoadingContainer> | ||
); | ||
}; | ||
|
||
const spin = keyframes` | ||
to { | ||
transform: rotate(360deg); | ||
} | ||
`; | ||
|
||
const LoadingContainer = styled.div` | ||
display: flex; | ||
justify-content: center; | ||
align-items: center; | ||
width: 100%; | ||
height: 100%; | ||
`; | ||
|
||
const Spinner = styled.div` | ||
border: 4px solid rgba(0, 0, 0, 0.1); | ||
border-left-color: #22a6b3; | ||
border-radius: 50%; | ||
width: 40px; | ||
height: 40px; | ||
animation: ${spin} 1s linear infinite; | ||
`; | ||
|
||
export default Loading; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,28 +1,93 @@ | ||
import styled from '@emotion/styled'; | ||
import { useState } from 'react'; | ||
import type { AxiosError } from 'axios'; | ||
import React, { useEffect, useState } from 'react'; | ||
|
||
import { Container } from '@/components/common/layouts/Container'; | ||
import EmptyData from '@/components/common/Status/emptyData'; | ||
import ErrorMessage from '@/components/common/Status/errorMessage'; | ||
import Loading from '@/components/common/Status/loading'; | ||
import { breakpoints } from '@/styles/variants'; | ||
import type { RankingFilterOption } from '@/types'; | ||
import { GoodsMockList } from '@/types/mock'; | ||
|
||
import { fetchData } from '../../../common/API/api'; | ||
import { GoodsRankingFilter } from './Filter'; | ||
import { GoodsRankingList } from './List'; | ||
|
||
export const GoodsRankingSection = () => { | ||
interface ProductData { | ||
id: number; | ||
name: string; | ||
imageURL: string; | ||
wish: { | ||
wishCount: number; | ||
isWished: boolean; | ||
}; | ||
price: { | ||
basicPrice: number; | ||
discountRate: number; | ||
sellingPrice: number; | ||
}; | ||
brandInfo: { | ||
id: number; | ||
name: string; | ||
imageURL: string; | ||
}; | ||
} | ||
|
||
interface FetchState<T> { | ||
isLoading: boolean; | ||
isError: boolean; | ||
errorCode?: string; | ||
errorMessage?: string; | ||
Comment on lines
+38
to
+40
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. error?: { code?: string; message?: string } error와 관련된 것들을 모아서 하나의 객체로 처리하면 어떨까요? |
||
data: T | null; | ||
} | ||
|
||
export const GoodsRankingSection: React.FC = () => { | ||
const [filterOption, setFilterOption] = useState<RankingFilterOption>({ | ||
targetType: 'ALL', | ||
rankType: 'MANY_WISH', | ||
}); | ||
|
||
// GoodsMockData를 21번 반복 생성 | ||
const [fetchState, setFetchState] = useState<FetchState<ProductData[]>>({ | ||
isLoading: true, | ||
isError: false, | ||
data: null, | ||
}); | ||
|
||
useEffect(() => { | ||
const fetchRankingProducts = async (filters: RankingFilterOption) => { | ||
setFetchState({ isLoading: true, isError: false, data: null }); | ||
try { | ||
const response = await fetchData('/api/v1/ranking/products', filters); | ||
setFetchState({ isLoading: false, isError: false, data: response.products }); | ||
} catch (error) { | ||
const axiosError = error as AxiosError; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 타입 단언이 좋은 선택일까요? |
||
setFetchState({ | ||
isLoading: false, | ||
isError: true, | ||
data: null, | ||
errorMessage: axiosError.message, | ||
errorCode: axiosError.code, | ||
}); | ||
} | ||
}; | ||
|
||
fetchRankingProducts(filterOption); | ||
}, [filterOption]); | ||
|
||
return ( | ||
<Wrapper> | ||
<Container> | ||
<Title>실시간 급상승 선물랭킹</Title> | ||
<GoodsRankingFilter filterOption={filterOption} onFilterOptionChange={setFilterOption} /> | ||
<GoodsRankingList goodsList={GoodsMockList} /> | ||
{fetchState.isLoading ? ( | ||
<Loading /> | ||
) : fetchState.isError ? ( | ||
<ErrorMessage code= {fetchState.errorCode} message={fetchState.errorMessage || '데이터를 불러오는 중에 문제가 발생했습니다.'} /> | ||
) : fetchState.data && fetchState.data.length > 0 ? ( | ||
<GoodsRankingList goodsList={fetchState.data} /> | ||
) : ( | ||
<EmptyData /> | ||
)} | ||
Comment on lines
+82
to
+90
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 삼항연산자를 중첩 사용하여 가독성이 좋지 않은 것 같아요. 조금 더 깔끔하게 표현해볼 수 있을까요? |
||
</Container> | ||
</Wrapper> | ||
); | ||
|
@@ -50,3 +115,5 @@ const Title = styled.h2` | |
line-height: 50px; | ||
} | ||
`; | ||
|
||
export default GoodsRankingSection; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
굳이 이렇게 만들어주는 이유가 있을까요?
에러 객체에서 필요한 정보가 코드와 설명 이외에도 있으면 어떻게 대응하실 예정일까요?