도리쓰에러쓰

[React] React Router 3 :: URL 파라미터로 상세페이지 100개 만들기 본문

코딩애플 (React)/기초수업 (코딩애플) - 2

[React] React Router 3 :: URL 파라미터로 상세페이지 100개 만들기

강도리 2022. 1. 12. 18:02

저번 게시물에 이어서 작성해보겠습니다. (코드 참고)

 

[React] React Router 2 :: Link, History, Switch 기능

저번 게시물에 이어서 작성해보겠습니다. (저번 게시물 코드 참고) [React] React Router 1 :: 세팅과 기본 라우팅 저번 게시물에 이어서 페이지를 나누는 React Router를 작성해보겠습니다. (저번 게시물

dori-coding.tistory.com


1. <Detail /> 컴포넌트에 데이터바인딩 하기

현재 Detail.js에 <Detail /> 컴포넌트가 존재하는데 '상품명', '상품 설명' 등이 임시로 출력되어 있습니다.

이 부분에 데이터바인딩을 해줄 예정입니다.

function Detail() {

    let history = useHistory();

    return(
        <div className='container'>
            <div className='row'>
            <div className='col-md-6'>
                <img className='img' src='images/img1.jpg' />
            </div>
            <div className='col-md-6 mt-4'>
                <h4 className='pt-5'>상품명</h4>
                <p>상품 설명</p>
                <p>1200000원</p>
                <button className='btn btn-danger'>주문하기</button>
                <br />
                <br />
                <button className='btn btn-danger' onClick={ () => {
                    history.goBack();
                }}>뒤로가기</button>
            </div>
            </div>
        </div>
    )
}

 

1) App.js에 작성되어있는 <Detail /> Component에 보낼 데이터를 props로 작성해줍니다.

 * 보낼이름 = {state명}

<Route path="/detail">
     <Detail products={ products }/>
</Route>

 

2) Detail.js에 parameter로 받아와 {parameter명.보낸이름} 으로 사용합니다.

function Detail(props) {

    let history = useHistory();

    return(
        <div className='container'>
            <div className='row'>
            <div className='col-md-6'>
                <img className='img' src='images/img1.jpg' />
            </div>
            <div className='col-md-6 mt-4'>
                <h4 className='pt-5'>{ props.products[0].title }</h4>
                <p>{ props.products[0].content }</p>
                <p>{ props.products[0].price }</p>
                <button className='btn btn-danger'>주문하기</button>
                <br />
                <br />
                <button className='btn btn-danger' onClick={ () => {
                    history.goBack();
                }}>뒤로가기</button>
            </div>
            </div>
        </div>
    )
}

 

다음과 같이 데이터가 출력되는 것을 확인할 수 있습니다.

 

📌 App.js에 선언되어 있는 products라는 state를 Detail.js로 props를 보내지 않고, Detail.js에 products state를 선언하여 props없이 사용할 수 있습니다. 하지만, 중요한 데이터는 App() Component에 보관하는게 정석입니다! 또한, 상위 컴포넌트(App.js)에서 하위 컴포넌트(Detail.js)로 데이터 보내는건 쉬운데 역방향으로 하위 컴포넌트(Detail.js)에서 상위 컴포넌트(App.js)로 데이터 보내는건 어렵습니다.

결론, 중요한 데이터는 App() Component (= 최상위 Component)에 보관합니다.

 

지금 데이터가 출력 되는 것은 하드코딩(ex. {props.products[0].title})하였기 때문에 출력된 것인데,

URL에 '/detail/0'에 접속하면 0번째 상품, '/detail/1'에 접속하면 1번째 상품, '/detail/2'에 접속하면 2번째 상품이 보이게 하겠습니다.

 

3) <Route path="/detail">를 <Route path="/detail/:id">로 변경합니다.

 * ':'은 '/detail/' 뒤에 아무 문자가 오던 간에 이 URL로 이동한다는 parameter 문법입니다.

  (id는 제 맘대로 작명한 것이고, paramter는 여러개도 작성 가능합니다. (ex. 'detail/:id/:id2'))

<Route path="/detail/:id">
    <Detail products={ products }/>
</Route>

 

4) Detail.js에 useParams()라는 훅을 import합니다.

import { useHistory, useParams } from 'react-router-dom';

 

5) useParams()를 선언합니다.

let { id } = useParams();

useParams를 작성하면 'id'라는 변수는 Object형이 되며, id 변수엔 사용자가 입력한 URL Parameter들이 들어갑니다.

Parameter가 여러개라면 let { id, id2, id3 } = useParams(); 이런식으로 작성하시면 됩니다.

 

6) id란 변수를 이용해 데이터바인딩을 합니다.

<div className='col-md-6'>
    <img className='img' src={ process.env.PUBLIC_URL + '/images/img'+ (Number(id) + 1)+'.jpg' } />
</div>
<div className='col-md-6 mt-4'>
    <h4 className='pt-5'>{ props.products[id].title }</h4>
    <p>{ props.products[id].content }</p>
    <p>{ props.products[id].price }</p>
    <button className='btn btn-danger'>주문하기</button>
    <br />
    <br />
    <button className='btn btn-danger' onClick={ () => {
        history.goBack();
    }}>뒤로가기</button>
</div>

Q. 만약 '인기순'으로 정렬을 변경하여 상품의 순서가 ['Black Jacket', 'White Bag', 'Black Bag']에서 ['White Bag', 'Black Jacket', 'Black Bag']으로 변경됐다면 '상세페이지' 0번째 데이터로 어떤게 떠야할까요?

 

A. '상세페이지'의 0번째 데이터로 'Black Jacket'가 출력되야 합니다. ('White Bag'으로 출력되면 안됩니다.)

 

Q. 만약, '상세페이지'의 0번째 데이터로 'White Bag'이 아닌 'Black Jacket'으로 출력되게 하고 싶다면?

 

A. 상품의 영구적인 번호를 활용하여 데이터바인딩하면 됩니다.

 

1) Data.js에서 find() 함수를 이용해 '현재 URL의 id값'과 '상품의 id값'이 같은 상품을 찾아 return해줍니다.

let findProduct = props.products.find(function(product) {
    return product.id = id;
});

 

2) 'findProduct' 변수를 활용해 데이터바인딩을 해주면 됩니다.

<div className='col-md-6'>
    <img className='img' src={ process.env.PUBLIC_URL + '/images/img'+ (Number(findProduct.id) + 1)+'.jpg' } />
</div>
<div className='col-md-6 mt-4'>
    <h4 className='pt-5'>{ findProduct.title }</h4>
    <p>{ findProduct.content }</p>
    <p>{ findProduct.price }</p>
    <button className='btn btn-danger'>주문하기</button>
    <br />
    <br />
    <button className='btn btn-danger' onClick={ () => {
        history.goBack();
    }}>뒤로가기</button>
</div>

2. 전체 코드

App.js

/* eslint-disable */
import React, { useState } from 'react'
import { Navbar, Container, Nav, NavDropdown, Button } from 'react-bootstrap';
import './App.css';
import data from './data.js';
import Detail from './Detail.js'

import { Link, Route, Switch } from 'react-router-dom';

function App() {

  let [products, setProducts] = useState(data);

  return (
    <div className="App">
      <Navbar bg="light" expand="lg">
        <Container>
          <Navbar.Brand href="#home">Saint Laurent</Navbar.Brand>
          <Navbar.Toggle aria-controls="basic-navbar-nav" />
          <Navbar.Collapse id="basic-navbar-nav">
            <Nav className="me-auto">
              <Nav.Link><Link to="/">Home</Link></Nav.Link>
              <Nav.Link><Link to="/detail">Detail</Link></Nav.Link>
              <NavDropdown title="Dropdown" id="basic-nav-dropdown">
                <NavDropdown.Item href="#action/3.1">Action</NavDropdown.Item>
                <NavDropdown.Item href="#action/3.2">Another action</NavDropdown.Item>
                <NavDropdown.Item href="#action/3.3">Something</NavDropdown.Item>
                <NavDropdown.Divider />
                <NavDropdown.Item href="#action/3.4">Separated link</NavDropdown.Item>
              </NavDropdown>
            </Nav>
          </Navbar.Collapse>
        </Container>
      </Navbar>

      <Switch>
        <Route exact path="/">
          <div className='Jumbotron'>
            <h1>20% Season Off</h1>
            <p>
              This is a simple hero unit, a simple jumbotron-style component for calling
              extra attention to featured content or information.
            </p>
            <p>
              <Button variant="primary">Learn more</Button>
            </p>
          </div>

          <div className='container'>
            <div className='row'>
              {
                products.map((a, i) => {
                  return <Card products={a} num={i} key={i}/>
                })
              }
            </div>
          </div>
        </Route>
        
        <Route path="/detail/:id">
          <Detail products={ products }/>
        </Route>
        
        <Route path="/:id">
            <div>아무말</div>
        </Route>
      </Switch>
    </div>
  );
}

function Card(props) {
  return(
    <div className='col-md-4'>
      <img className="img" src={ 'images/img'+ (props.num + 1) +'.jpg' } />
      <h4>{ props.products.title }</h4>
      <p>{ props.products.content }</p>
      <p>{ props.products.price }</p>
    </div>
  )
}

export default App;

 

Detail.js

import React, { useState } from 'react';
import { useHistory, useParams } from 'react-router-dom';

function Detail(props) {

    let { id } = useParams();
    let history = useHistory();
    let findProduct = props.products.find(function(product) {
        return product.id = id;
    });

    return(
        <div className='container'>
            <div className='row'>
            <div className='col-md-6'>
                <img className='img' src={ process.env.PUBLIC_URL + '/images/img'+ (Number(findProduct.id) + 1)+'.jpg' } />
            </div>
            <div className='col-md-6 mt-4'>
                <h4 className='pt-5'>{ findProduct.title }</h4>
                <p>{ findProduct.content }</p>
                <p>{ findProduct.price }</p>
                <button className='btn btn-danger'>주문하기</button>
                <br />
                <br />
                <button className='btn btn-danger' onClick={ () => {
                    history.goBack();
                }}>뒤로가기</button>
            </div>
            </div>
        </div>
    )
}

export default Detail;

 

* 이외의 코드는 이전 게시물 코드와 동일합니다.

Comments