[chapter 6] 모달 및 이미지 불러오기 - 2

서희찬·2022년 3월 20일
1

The Origin : React

목록 보기
12/17
post-thumbnail

React Router Dom

웹 앱에서 동적 라우팅을 구현할 수 있다.
라우팅이 실행 중인 앱 외부의 구성에서 처리되는 기존 라우팅 아키텍처와 달리 React Router DOM은 앱 및 플랫폼의 요구 사항에 따라 컴포넌트 기반 라우팅을 용이하게 합니다.

Single Page Application(SPA)

리액트는 SPA!!

React Router Dom 설치하기

npm install react-router-dom --save 
yarn add react-router-dom 

React Router 설정하기

설치가 완료된 후 가장 먼저 할 일은 앱 어디에서나 React Router를 사용할 수 있도록 하는 것
이렇게 하려면 src폴더에서 index.js 파일을 열고 react-router-dom에서 BrowserRouter를 가져온 다음 루트 구성요를 그 안에 래핑

여러 컴포넌트 생성 및 라우트 정의하기
To로 가기때문에 Link랑 비슷한듯..!?

React Router Dom APIs

중첩라우팅

useNavigate

경로를 바꿔준다.

useParams

style 문법을 path경로에서 사용하였다면 useParams()로 읽을 수 있다.
아래는 :invoicedId가 무엇인지 알기위해 useParams를 사용했다.

useLocation

이 Hooks는 현재 위치 객체를 반환한다. 이것은 현재 위치가 변경될 때마다 일부 side effect를 수행하려는 경우에 유용할 수 있다.

Netflix 앱에 React Router Dom 적용하기

구현할 부분

  1. 검색페이지
  2. 디테일페이지

페이지 생성을 위한 폴더 및 파일 추가

Django를 사용할때처럼 파트별로 파일을 빼내는것이다.
그래서

  • MainPage
  • SearchPage
  • DetailPage
    를 각각 빼내주면 App.js파일은 아래와같이 된다.
App.js
import './App.css';
import Nav from "./components/Nav";
import Footer from './components/Footer';
import { Outlet, Route, Routes } from 'react-router-dom';

import DetailPage  from './pages/DetailPage';
import MainPage from './pages/MainPage';
import SearchPage from './pages/SearchPage';

const Layout = () =>{
  return(
    <div>
      <Nav/>

      <Outlet/>

      <Footer/>

    </div>
  )
}


function App() {
  return (
    <div className="App">
      <Routes>
        <Route path='/' element={<Layout/>}>
          <Route index element={<MainPage/>} />
          <Route path=":movieId" element={<DetailPage/>} />
          <Route path="search" element={<SearchPage/>} />

        </Route>
      </Routes>

    </div>
  );
}

export default App;

라우터로 감싸주고 페이지리를 불러오는 형식이다.
path같은 경우는 도메인뒤에 붙는 것이라고 생각하면 된다.
Layout에는 모든 페이지에 공통적으로 들어갈 Navbar,footer를 넣고 그 외에는 pages에 따로 빼준다.
이런 식으로 말이다!
그래서 MainPage/index.js 파일을 한번 보면

import React from 'react'
import Banner from '../../components/Banner'
import Row from '../../components/Row'
import requests from '../../api/requests'

export default function MainPage() {
  return (
    <div>
              <Banner/>

<Row 
  title="NETFLIX ORIGINALS"
  id="NO"
  fetchUrl={requests.fetchNetflixOriginals}
  isLargeRow
/>

<Row
  title="Trending Now"
  id = "TN"
  fetchUrl={requests.fetchTrending}
/>
<Row
  title="Top Rated"
  id = "TR"
  fetchUrl={requests.fetchTopRated}
/>
<Row
  title="Action Movies"
  id = "AM"
  fetchUrl={requests.fetchActionMovies}
/>
<Row
  title="Comnedy Movies"
  id = "CM"
  fetchUrl={requests.fetchComedyMovies}
/>
    </div>
  )
};

이런 식으로 작성하면된다.
그런데 여기서 끝이 아니라 index.js로 가서

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { BrowserRouter } from 'react-router-dom';

ReactDOM.render(
  <BrowserRouter>
    <App />
  </BrowserRouter>,
  document.getElementById('root')
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

이런식으로 BrowserRouter로 감싸줘야한다.

검색 페이지 구현

 <input value={searchValue} onChange={handleChange} className="nav__input" type="text" placeholder='영화제목을 입력해주세요.'></input>

input을 생성해준다.
생성해주고css를 입혀주고

    const [searchValue, setsearchValue] = useState("");
    const navigate = useNavigate();
    
    const handleChange = (e) => {
        setsearchValue(e.target.value); //바로 검색가능하다. 
        navigate(`/search?q=${e.target.value}`)
    };

함수를 줘서 입력하면 바로 검색되게 만든다.

이제 경로에서 value를 가져와서 전달해주자.
이때 useLocation을 사용한다.

q에 있는걸 가져오게 하면 다음과 같이 뜬다.

import React from 'react'
import { useLocation } from 'react-router-dom'

export default function SearchPage() {
    console.log('useLocation() : ',useLocation());
    const useQuery =()=>{
        return new URLSearchParams(useLocation().search);
    }

    let query = useQuery();
    const searchTerm = query.get("q"); 
  return (
    <div>SearchPage</div>
  )
}

SearchTerm이 바뀔 때마다 새로 영화 데이터 가져오기

import axios from "../../api/axios";
import React, { useEffect, useState } from 'react';
import { useLocation } from 'react-router-dom'

export default function SearchPage() {

    const [searchResult, setsearchResult] = useState([]);

    
    // console.log('useLocation() : ',useLocation());
    const useQuery =()=>{
        return new URLSearchParams(useLocation().search);
    }

    let query = useQuery();
    const searchTerm = query.get("q");  //q에 있는걸 가져온다. 
    // console.log(searchTerm)

    useEffect(() => {
        
        if(searchTerm){
            fetchSearchMovie(searchTerm);
        }
    }, [searchTerm]);

    const fetchSearchMovie = async (searchTerm) =>{
        try{
            const request = await axios.get(
                `/search/multi?include_adult=false&query=${searchTerm}`
            )
            setsearchResult(request.data.results);
        }catch(error){
            console.log("ERROR",error);

        }
    }

    return (
    <div>SearchPage</div>
  )
}

매번 검색될때마다 새로운 영화데이터를가져온다.
그러면 영화가 있을때와 없을때의 UI를 이제 작성해주자.

검색페이지 UI 구현하기

SearchTerm에 해당 영화 데이터가 있을 경우

SearchTerm에 해당 영화 데이터가 없을 경우


이는 삼항연산자를 통해 해결했다.

import axios from "../../api/axios";
import React, { useEffect, useState } from 'react';
import { useLocation, } from 'react-router-dom'
import "./SearchPage.css";


export default function SearchPage() {

    const [searchResult, setsearchResult] = useState([]);

    
    const useQuery =()=>{
        return new URLSearchParams(useLocation().search);
    };

    let query = useQuery();
    const searchTerm = query.get("q");  //q에 있는걸 가져온다. 
    // console.log(searchTerm)

    useEffect(() => {
        
        if(searchTerm){
            fetchSearchMovie(searchTerm);
        }
    }, [searchTerm]);

    const fetchSearchMovie = async (searchTerm) =>{
        try{
            const request = await axios.get(
                `/search/multi?include_adult=false&query=${searchTerm}`
            )
            setsearchResult(request.data.results);
        }catch(error){
            console.log("ERROR",error);

        }
    }

    const renderSearchResults = () =>{
        return searchResult.length > 0 ? (
            <section className="search-container">
                {searchResult.map((movie)=>{
                    if(movie.backdrop_path !== null && movie.media_type !== "person"){
                        const movieImageUrl = 
                        "https://image.tmdb.org/t/p/w500" + movie.backdrop_path;
                        return(
                            <div className="movie">
                                <div className="movie__column-poster">
                                    <img src={movieImageUrl} alt="movie" className="movie__poster">
                                    </img>
                                </div>
                            </div>
                        )
                    }
                })}

            </section>
        ) : (
        <section className="no-results">
            <div className="no-results__text">
                <p>
                    찾고자하는 검색어"{searchTerm}"에 맞는 영화가 없습니다.
                </p>
            </div>

        </section>
        );
    };

    return renderSearchResults();
}

useDebounce Custom Hooks 만들기

Debounce가 무엇인가요?


불필요한 로드를 줄여준다.
hooks에 useDebouce.js파일을 만들어준다.

import { useState,useEffect } from 'react'

export const useDebounce=(value,delay)=>{
    const [DebounceValue,setDebounceValue] = useState(value);
    useEffect(() => {
      const handler = setTimeout(()=>{
          setDebounceValue(value)
      },delay);
      return () => {
        clearTimeout(handler);
      };
    }, [value, delay])

    return DebounceValue;
}

이를 가져가서 searchPage에서 사용하면된다.

const debouncedSearchTerm = useDebounce(searchTerm, 500);

이런 방식으로 말이다!
그리고, 나머지 searchTerm을 debouncedSearchTerm으로 바꿔주고 movie마다 key를 주면
에러없이 매번 로드하는것이 아니라 내가 정해놓은 시간마다 로드하는것을 확인할 수 있다.

영화상세 페이지 표현

<div onClick={()=>navigate(`/${movie.id}`)} className="movie__column-poster">

이를 통해서 영화detail페이지로 영화클릭시 이동 가능하다.

그 이후
detailpage의 index.js 파일을 작성해주자

import axios from "../../api/axios"
import React, { useEffect, useState } from 'react'
import { useParams } from 'react-router-dom'

export default function DetailPage() {
    const {movieId} = useParams();
    const [movie, setMovie] = useState({});
    useEffect(() => {
    async function fetchData(){
        const request = await axios.get(
            `/movie/${movieId}`
        )
        setMovie(request.data)
    }



    fetchData();
    }, [movieId])

    if(!movie) return <div>...loading</div>;

    return <section>
        <img
        className="modal__poster-img"
        src={`https://image.tmdb.org/t/p/original/${movie.backdrop_path}`}
        alt="poster"
        />
    </section>
}

이와 같이 작성하면
이미지가 있는 경우 이렇게 이미지를 뜨게 만들어준다.

profile
부족한 실력을 엉덩이 힘으로 채워나가는 개발자 서희찬입니다 :)

0개의 댓글