본문 바로가기

공부일지/Project

비전공자의 프로젝트 만들기) Disneyplus App 만들기 part3

728x90
반응형

Disneyplus App 만들기 part3

 

이번파트는 우선 터미널에서 react-router-dom을 설치한다.

 

react-router-dom이 무엇이냐면 간단히 설명하자면 유저가 원하는 페이지를 보여주는 기능을 하는 거라 생각하면 된다.

 

 

root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

root.render(
  <BrowserRouter>
    <App />
  </BrowserRouter>
);

설치하면 루트경로에 index.js파일을 보면 React.StrictMode로 App가 감싸져 있는데 그 부분을 BrowserRouter로 변경해 준다.

 

그리고 App.js에 가서

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

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

아래와 같이 Routes로 감싸주고 path로 해당 주소로 변경이 되었을 때 나와야 되는 화면을 출력해 준다.

그리고 각 page들은 관리하기 편하도록 pages라는 폴더를 만들고 관리해 준다.

Route를 보면 Layout라는 엘리먼트는 다른 Route를 감싸고 있는데 그 부분은 Layout이라고 생각하면 된다.

html을 해보았다면 ifram이라는 엘리먼트가 있는데 그런 느낌과 비슷하다. 레이아웃이 있고 레이아웃 안에 필요한 컴포넌트를 출력하는 것이다. 

기존에 app.js에 있던 소스코드들을 mainPage안에 index.js파일 안에 넣은 후 import 경로를 수정해 준다.

 

이제 해당 프로젝트에서 작업시간이 제일 오래 걸린 검색 부분 작업이다.

component폴더 안에 Nav파일을 열어 기존에 작성했단 input 영역과 입력 시 서치페이지로 이동해서 해당 결과를 출력하는 기능을 작업한다.

 

  
  const { pathname } = useLocation();
  const [searchValue, setSearchValue] = useState("");
  const navigate = useNavigate();
  
  const handleChange = (e) => {
    setSearchValue(e.target.value);
    navigate(`/search?q=${e.target.value}`)
  }

  
  <Input 
    value={searchValue}
    onChange={(e)=>handleChange(e)}
    className="nav__input"
    type="text"
    placeholder="검색해 주세요"
  />

react-router-dom에서 useLocation을 사용할 수 있다.

useLocation에서 pathname이라는 속성을 사용할 수 있는데 pathname은 현재 url에서 pathname을 내보내준다. 

예) url/main → /main을 반환해 줌 

 

input에서 onChange에서 handleChange 이벤트를 만들고 해당 이벤트에서 검색어를 저장하기 위해 만든 searchValue에 값을 저장 후  navigate를 사용해야 하는데 navigate란 react-route-dom에서 사용할 수 있는데 url을 변경해 주는 기능이다.

저번에 app.js에서 /search 페이지는 검색페이지라고 지정을 해놨기 때문에 input에 검색어를 작성 후 바로 search페이지로 이동한다.

 

위의 코드를 보면 우리가 입력할 때마다 state에 저장되며 입력과 동시에 search페이지로 이동하게 된다. 그럼 이제 search페이지에 대해 작업을 진행해보자 해당 프로젝트는 완성한 코드를 블로그에 리뷰 형식으로 작성하는 거라 search 파일에 대해 설명하면서 나중에 사용하는 코드인데 작성된 코드들도 있다.

 

const SearchPage = () => {

  const [searchResults, setSearchResults] = useState([])

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

  let query = useQuery();
  const searchTerm = query.get('q');

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

  if(searchResults.length > 0){
    return (
      <section className='search-container'>
        {searchResults.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' key={movie.id}>
                <div className='movie__column-poster' onClick={()=> navigate(`/${movie.id}`)}>
                  <img src={movieImageUrl} alt="movie" className='movie__poster' />
                </div>
              </div>
            )
          }else{
          }
        })}
      </section>
    )
  }else{
    return(
      <section className='no-results'>
        <div className='no-results__text'>
          <p>찾고자하는 검색어 "{searchTerm}"에 맞는 영화가 없습니다.</p>
        </div>
      </section>
    )
  }

}

우선 검색했을 때 리스트들을 담을 state인 searchResults를 만들고 api데이터를 보내 해당 결과를 전달받기 위해 파라미터를 저장할 useQuery를 만든다 

useQuery에 사용한 useLocation().search는 url에서 ? 뒤에 있는 파라미터들을 반환해 준다.

그리고 URLSearchParams를 사용해 파라미터들을 key, value로 저장한 후 그 결괏값을 query에 저장한다.

서치파일에서 searchTerm이 제일 중요한 변수인 거 같다. 

query에서 q로 시작되는 파라미터를 찾아  저장한다. 위에서 설명을 못했는데 검색할 때 navigate에서 /search?q= 로 시작하게 만들었기 때문에 q라는 key값이 있다.

 

저번에 만들었던 movieApi를 불러와 서치 요청을 보내준다. 해당 url은 the moive db에서 정의한 url이다.

이제 결괏값을 state에 저장 후 해당 state의 값이 있으면 리스트를 출력해 주고 없으면 영화가 없다는 문구를 출력해 준다.

코드를 보면 poster에 onClick 이벤트가 있는데 그 부분은 추후 영화 상세정보기 작업을 할 때 진행할 예정이다.

 

map으로 하나씩 출력하는 코드를 보면 따로 어려운 부분은 없어서 상세 설명은 넘어간다.

 

해당 코드를 작성하고 검색을 해보면 서치 결과가 나오는데 뒤로가기를 누르면 이상한 점이 있을 거다.

한 글자씩 입력할 때마다 우리 눈에는 안보이지만 페이지가 이동되고 있기 때문에 한글자 한글자씩 지워진다. 그렇기 때문에 문자를 입력할때마다 페이지가 이동하지 않게 막아야 된다.

 

그럴 때 사용하는데 deBounce라는 커스텀훅스 이다.

useDebounce.js

import { useEffect, useState } from "react";

export const useDebounce = (value, delay) => {
    const [debouncedValue, setDebouncedValue] = useState(value);

    useEffect(() => {
        const handler = setTimeout(() => {
            setDebouncedValue(value);
        }, delay);

        return () =>{
            clearTimeout(handler)
        }
    },[value, delay])

    return debouncedValue
}

색다른 코드가 있는 건 아니고 글자 입력 이벤트가 들어올 때마다 실행되는 기능을 만들어 주면 된다.

입력받은 값을 저장한 state를 만든 후 setTime으로 일정시간 동안 입력이 없으면 state에 저장이 되고 해당 state가 반환 된다.

clearTimeout가 들어간 이유는 글자가 입력이 감지되면 이전에 선언했던 setTime이벤트를 지우고 다시 선언을 해야 정상적으로 이벤트가 실행된다 clear를 하지 않으면 무조건 해당 시간이 되면 입력하고 있는 동안이랑 상관없이 value값이 반환된다.

 

해당 이벤트를 검색에서 사용하기 위해 search파일로 간다.

  let query = useQuery();
  const searchTerm = query.get('q');
  const debounceSearchTerm = useDebounce(searchTerm, 500);

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

searchTerm을 만든 영역 밑에 debounce를 만들어주고 useEffect를 사용해 해당 이벤트가 완료되면 검색기능이 실행되게 만들었다. 

 

이렇게 만들면 이제 검색기능은 완료되었다 이제 firebase연동을 해서 로그인기능과 서비스 배포 부분을 작업할 예정이다.

 

728x90
반응형