[React] react-router-dom useParams 도입기 - 전체 항목 페이지에서 세부 항목 페이지로 전환하기

느려도 꾸준한 발걸음·2024년 7월 11일
0
post-thumbnail

1. 문제 상황

여러 개의 항목 리스트가 존재하는 화면에서,
특정 항목을 누르면 그 항목에 대한 추가 설명이 있는 detail한 페이지로 화면을 전환하고 싶습니다.

조금 더 구체적으로, 아래와 같이 게시물의 id를 url에 동적으로 전달하여
전체 게시글이 로드되는 화면에서 각 게시물을 클릭하면 해당 게시물만이 보이는 페이지로 이동하도록 구현하고자 합니다.

아래는 사용자들이 게시판에 업로드한 모든 게시물을 렌더링하는 컴포넌트의 코드입니다.

import React, { useState, useEffect } from "react";
import { Link } from "react-router-dom";

const Post = () => {
  const [post, setPost] = useState([]);

  useEffect(() => {
    const postDataOriginUrl = "http://127.0.0.1:8000/api/";
    fetch(postDataOriginUrl)
      .then((resopnse) => resopnse.json())
      .then((data) => setPost(data));
  }, []);

  console.log(post);

  return (
    <div>
      {post.map((posting) => (
        <Link to={/*경로 추가 필요*/}`}>
          <div key={posting.id}>
            <li>{posting.title}</li>
            <li>{posting.content}</li>
            <li>{posting.author}</li>
            <li>{posting.author}</li>
          </div>
        </Link>
      ))}
    </div>
  );
};

export default Post;

프로젝트의 전체적인 라우팅 구조는 아래와 같습니다.

import { BrowserRouter, Route, Routes } from "react-router-dom";
import PostingFeedUI from "./pages/Main";
import React from "react";
import Register from "./components/Register";
import Login from "./components/Login";
import PostDetail from "./components/PostDetail";

function App() {
  return (
    <>
      <BrowserRouter>
        <Routes>
          <Route path="/" element={<PostingFeedUI />} />
          <Route path="/:id" element={<PostDetail />} />
          <Route path="/register/" element={<Register />} />
          <Route path="/login/" element={<Login />} />
        </Routes>
      </BrowserRouter>
    </>
  );
}

export default App;

게시물의 id를 동적으로 전달하여, PostDetail 컴포넌트에서 하나의 게시물만을 보여주도록 하고 있습니다.

특정 게시물을 클릭했을 때,
화면에 해당 게시물만 표시되도록 하려면 어떻게 해야 할까요?

2. 해결 방안

가장 먼저, 해당 게시물 인스턴스의 모든 프로퍼티들을 prop으로 전달하는 방법이 생각납니다. 이 역시 가능한 방법이지만, 프로젝트가 커지면서 로직이 복잡해질 수도 있겠다는 생각이 듭니다.

useParams를 사용하면, 아주 간단하게 url에 동적으로 전달한 변수를 구조 분해 구문을 사용해 받아와 사용할 수 있습니다.

우선, Link태그의 to 속성 경로를 다음과 같이 설정해줍니다.

<Link to={`/${posting.id}`}>

이제, 특정 게시물을 클릭하면 로컬호스트 주소/id 엔드포인트로 url을 변경합니다.

다시금 프로젝트의 라우팅 구조를 살펴보면,

	 <BrowserRouter>
        <Routes>
          <Route path="/" element={<PostingFeedUI />} />
          <Route path="/:id" element={<PostDetail />} />
          <Route path="/register/" element={<Register />} />
          <Route path="/login/" element={<Login />} />
        </Routes>
      </BrowserRouter>

로컬호스트 주소/id 경로에서는 <PostDetail /> 컴포넌트가 렌더링됩니다.

즉, Link태그를 통해 <PostDetail /> 컴포넌트로 id 라는 값을 전달하고 있는 것입니다. 이제, <PostDetail /> 컴포넌트에서 useParams를 사용해 id값에 접근할 수 있습니다.

컴포넌트의 전체 코드는 아래와 같습니다.

import React, { useState, useEffect } from "react";
import { Link } from "react-router-dom";

const Post = () => {
  const [post, setPost] = useState([]);

  useEffect(() => {
    const postDataOriginUrl = "http://127.0.0.1:8000/api/";
    fetch(postDataOriginUrl)
      .then((resopnse) => resopnse.json())
      .then((data) => setPost(data));
  }, []);

  console.log(post);

  return (
    <div>
      {post.map((posting) => (
        <Link to={`/${posting.id}`}>
          <div key={posting.id}>
            <li>{posting.title}</li>
            <li>{posting.content}</li>
            <li>{posting.author}</li>
            <li>{posting.author}</li>
          </div>
        </Link>
      ))}
    </div>
  );
};

export default Post;

이후, useParams를 사용해 Link에서 전달한 id값을 받아와야 합니다.
아래와 같이 구조 분해 할당 구문을 사용해 url에 동적으로 전달한 변수 id를 가져올 수 있습니다.

 const { id } = useParams();

이제 전체 게시글이 아닌, 이 id에 해당하는 게시글 하나만을 불러와달라고 서버에 요청을 보내주면 됩니다.

useEffect(() => {
    const postDataOriginUrl = `http://127.0.0.1:8000/api/${id}/`;
    fetch(postDataOriginUrl)
      .then((resopnse) => resopnse.json())
      .then((data) => setPost(data));
  }, [id]);

위와 같이, fetch의 엔드포인트에 useParams로 가져온 id를 추가하였습니다.

아래는 id별로 하나의 게시물을 보여주는 컴포넌트의 전체 코드입니다.

import React, { useState, useEffect } from "react";
import { useParams } from "react-router-dom";

export default function PostDetail() {
  const { id } = useParams();
  const [post, setPost] = useState({});

  useEffect(() => {
    const postDataOriginUrl = `http://127.0.0.1:8000/api/${id}/`;
    fetch(postDataOriginUrl)
      .then((resopnse) => resopnse.json())
      .then((data) => setPost(data));
  }, [id]);

  console.log(post);

  return (
    <div>
      <h2>{post.title}</h2>
      <p>{post.content}</p>
      <p>{post.author}</p>
    </div>
  );
}

이렇게 함으로써 서버는 클라이언트가 요청한 id값을 가진 게시물 하나만을 데이터베이스에서 찾아 브라우저로 보내줍니다.

이후 이 게시물을 다시 post state에 저장하고, 이후 렌더링하는 구조입니다.

3. 마무리 및 정리 (feat. 언제 사용하면 좋은가?)

개발을 하다 보면,
전체 항목을 화면에 띄우고, 사용자가 각 항목을 누르면 그 항목에 맞는 디테일 페이지로 화면을 전환해야 하는 상황이 자주 발생합니다.

간단히 예를 들어보면, 쇼핑몰이라면 처음 사용자가 접속했을 때 여러 상품이 보이고, 원하는 상품을 누르면 해당 상품의 상세 정보와 주문하기 버튼이 있는 페이지가 렌더링 되어야 하는 상황 등이 있겠네요.

이 경우, 항목마다 고유한 값(DB에서 primary key로 사용되고 있는 값 등)을 url에 동적으로 전달하고, 그 값을 useParams 메소드로 불러와 개별 항목을표시하는 컴포넌트 내에서 사용하면 되겠습니다.

profile
웹 풀스택 개발자를 준비하고 있습니다. MERN스택을 수상하리만큼 사랑합니다.

0개의 댓글