나만의 맛집 페이지를 구현하는 프로젝트
로딩 처리와 에러 처리를 포함해, 사용자 경험을 개선할 기능을 추가합니다. 또한, 사용자의 위치 데이터를 활용해 맛집을 가까운 순서로 정렬하는 기능을 구현합니다.
EATINGMARK-FE
├── src/
│ ├── components/
│ ├── pages/
│ ├── context/
│ ├── api/
│ ├── styles/
│ └── App.jsx
└── public/
로딩중이라는 메시지 화면에 출력에러메시지 화면에 출력📁 App.jsx
import { Link, Route, Routes, useNavigate } from "react-router"
import { Main } from "./pages/Main"
import { Detail } from "./pages/Detail"
import { Favorite } from "./pages/Favorite"
import styles from "./styles/app.module.scss"
import { useEffect, useState } from "react"
import { getPlaces, getUserPlaces } from "./api/placeApi"
import { Error404, Loading } from "./components/LoadingError"
function App() {
const navigate = useNavigate()
const [places, setPlaces] = useState([])
const [fav, setFav] = useState([])
const [isError, setIsError] = useState(null)
const [isPending, setIsPending] = useState(false)
useEffect(()=> {
const fetchData = async () => {
try{
setIsError(null)
setIsPending(true)
const allPlace = await getPlaces();
const favPlace = await getUserPlaces()
setPlaces(allPlace)
setFav(favPlace)
} catch (err) {
setIsError("Loading Fail ...", err)
} finally {
setIsPending(false)
}
}
fetchData()
},[])
return (
<>
{/* &&는 왼쪽이 true일 때만 오른쪽 값을 반환 */}
{ isError && <Error message={isError} /> }
{ isPending && <Loading /> }
{!isError && !isPending && (
<>
<h1 className={styles.h1}
onClick={() => navigate('/') }
> Eating Mark ! 🍓 </h1>
<nav className={styles.nav}>
<Link to={'/'}> Main </Link>
<Link to={'/detail'}> Detail </Link>
<Link to={'/favorite'}> Favorite </Link>
</nav>
<main className={styles.main}>
<Routes>
<Route path={"/"} element={<Main places={places}/>}/>
<Route path={"/detail/:placeId"} element={<Detail />}/>
<Route path={"/favorite"} element={<Favorite fav={fav}/>}/>
</Routes>
</main>
</>
)}
</>
)
}
export default App
<Error message={isError} /> 적용 , 로딩 중일 때 <Loading /> 적용📁 Loading.jsx
import styles from '../styles/loadingerror.module.scss'
export const Error = ({message}) => {
return <div className={styles.error}>❗ {message}</div>;
}
export const Loading = () => {
return <div className={styles.loading}> ⏳ 로딩 중입니다... </div>;
}
📁 App.jsx
<Route path={"*"} element={<Error404 />}/>
📁 Loading.jsx
export const Error404 = () => {
return <div className={styles.error}> 404 ERROR </div>;
}
loc.js 파일 참고📁 Main.jsx
import { useEffect, useState } from "react"
import { Card } from "../components/Card"
import { sortPlacesByDistance } from "../components/loc"
import styles from "../styles/main.module.scss"
import { Favorite } from "./Favorite"
export function Main ({places}) {
const [userLocation, setUserLocation] = useState(null)
const [distance, setDistance] = useState(false)
useEffect(() => {
navigator.geolocation.getCurrentPosition((position) => {
setUserLocation({
lon: position.coords.longitude,
lat: position.coords.latitude
})
}, (err) => {
console.error("위치 정보를 가져오는 데 실패했어요!", err)
})
},[])
const sortedPlaces = userLocation ? sortPlacesByDistance(places, userLocation.lat, userLocation.lon)
: places
return(
<div className={styles.firdiv}>
<Favorite/>
<div className={styles.secdiv}>
<h1 className={styles.h1}> All Place ... </h1>
<button className={styles.button}
onClick={() => setDistance(!distance)}> {distance ? "기본순" : "거리순"}</button>
</div>
<div className={styles.thrdiv}>
{distance ?
sortedPlaces.map((place) => <Card key={place.id} place={place}/>)
: places.map((place) => <Card key={place.id} place={place}/>)}
</div>
</div>
)
}
userLocation : 사용자 위치 정보 받아오는 상태 함수distance : 거리순 또는 기본순으로 보기 위해 받는 상태 함수navigator.geolocation.getCurrentPosition : 브라우저 메소드 이용하여 위치 정보 받아오기