[React] 맛집 리스트 PJ - (2)

박하늘·2025년 4월 15일

Project

나만의 맛집 페이지를 구현하는 프로젝트

📍 2단계

로딩 처리에러 처리를 포함해, 사용자 경험을 개선할 기능을 추가합니다. 또한, 사용자의 위치 데이터를 활용해 맛집을 가까운 순서로 정렬하는 기능을 구현합니다.

  • ReactNode.js 서버를 사용합니다.
  • 이전 단계에서 설계한 레이아웃 및 API 호출 로직을 기반으로 구현을 진행하세요.

프로젝트 구조

EATINGMARK-FE
├── src/
│   ├── components/
│   ├── pages/
│   ├── context/
│   ├── api/
│   ├── styles/
│   └── App.jsx
└── public/



1️⃣ Loading 및 기본 Error 처리

  • Loading 일 경우 로딩중이라는 메시지 화면에 출력
  • Error 일 경우 에러메시지 화면에 출력

📁 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
  • 에러와 로딩 관리하는 상태 변경 함수 만들기 - useState
  • try , catch , finally 사용하여 로딩 및 에러 관리
  • 에러 발생 시 <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>;
}
  • 에러와 로딩 발생 시 출력 될 화면 컴포넌트 생성해주기

🔺 네트워크 속도를 Slow 3G로 설정한 상태에서 테스트




2️⃣ 404 에러 처리

  • 유효하지 않은 서버 주소나 잘못된 요청으로 인해 발생하는 에러(예: 404)를 처리

📁 App.jsx

<Route path={"*"} element={<Error404 />}/>
  • 위 Route 추가 해주기
  • 지정된 경로 외의 경로로 접속 시 404 Error 발생

📁 Loading.jsx

export const Error404 = () => {
    return <div className={styles.error}> 404 ERROR </div>;
}
  • 에러 화면 구현 컴포넌트로 404 error 처리 화면 추가해주기



3️⃣ 위치 접근 허용하여 거리순 나열

  • 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 : 브라우저 메소드 이용하여 위치 정보 받아오기
  • 버튼 클릭 시 distance 값이 반대로 바뀌고 일 때는 거리순으로, 거짓일 때는 기본순으로 볼 수 있게 삼항 연산자 사용

0개의 댓글