(점점 울고 싶어지지만 일단 간다)
1. useLocation을 이용한 검색 페이지 구현
이런 식으로 Nav에 넷플릭스 로고와 유저 이미지 가운데로 Input 박스를 넣어 검색어를 받아주려고 한다.
Nav.js에 가서 const [searchValue, setSearchValue] = useState("")를 작성해준다.
이후 상단 UI 부분에 input을 적어주되 value={searchValue} (=초기 ""), onChange={handleChange}를 같이 적어준다.
이 함수는 아래와 같이 인풋 박스에 무언가 적힐 때마다 그 값으로 searchValue를 바꿔주고, 경로를 이동 시킨다.
q는 query의 약어로 해당 파라미터는 검색어를 나타낸다. 즉 /search 경로로 이동하면서 검색어를 이 이벤트 값으로 설정한다.
const handleChange = (event) => {
setSearchValue(event.target.value);
navigate(`/search?q=${event.target.value}`);
};
search 쪽 컴포넌트로 가보자.
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></div>;
}
useLocation을 사용해 현재 URL의 정보를 반환하는 객체를 받는다.
콘솔에 찍어보니 아래 이미지와 같이 나온다.
d를 입력했을 때 search에 ?q=d가 찍혀 있다.
useQuery 함수에서는 URL의 쿼리 파라미터를 가져온다.
이전 게시글에서 useLocation을 호출하고 .search를 하면 쿼리 문자열을 가져올 수 있다고 했다.
그 부분을 searchTerm으로 할당했다.
useEffect(() => {
if (searchTerm) {
fetchSearchMovie(searchTerm);
}
}, [searchTerm]); //의존 배열에 넣은 이유는 검색어를 입력하는 동안에도 해당되는 것들을 보여주게 하기 위함
const fetchSearchMovie = async (searchTerm) => {
try {
const request = await axios.get(
`/search/multi?include_adult=false&query=${searchTerm}`
);
console.log(request);
setSearchResults(request.data.results);
} catch (error) {
console.log("error", error);
}
};
useEffect로 검색어가 변경될 때마다 fetchSearchMovie 함수를 호출하고 있다.
fetchSearchMovie 함수는 searchTerm을 매개변수로 받아와 해당 검색어를 사용해 영화 검색 API를 호출한다.
axios를 사용해서 해당 엔드포인트에 GET 요청을 보내고, 쿼리 파라미터로 그 뒷부분을 보내고 있다. (성인 영화 X, 검색어 해당되는 것)
이렇게 해서 값을 받아오면 setSearchResults 함수에 검색 결과를 업데이트한다.
(const [searchResults, setSearchResults] = useState([]))
콘솔에 찍은 부분이 request.data.results 부분이다.
2. 검색 페이지 UI 구현
search Term에 영화 데이터가 있을 경우에는 map으로 영화를 하나씩 꺼내 이미지를 보여줄 것이다.
없다면 니가 검색한 것에 해당되는 영화 없다 라고 알려줄 멘트가 필요하다.
지금 스파이더맨을 검색했을 때 searchResults가 나오고 있다. 스파이더맨에 해당되는 영화들이다.
length가 20인데, 만약 검색했을 때 값이 없다면 length는 0일 것이다.
따라서 이 길이를 삼항연산자로 다뤄 길이가 0보다 크다면 해당 영화들을 보여주고, 그렇지 않다면 없다 라는 멘트를 보여주려고 한다.
const renderSearchResults = () => {
return searchResults.length > 0 ? ( //데이터 이거 있어 없어
<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">
<div className="movie__column-poster">
<img
className="movie__poster"
src={movieImageUrl}
alt="movie"
/>
</div>
</div>
);
}
})}
</section>
) :
지금 0보다 커 안 커? 했을 때 커! 부분만 짤라왔다.
이럴 때 UI는 이 검색한 내용들을 map으로 하나씩 꺼내줘야 하는데, 조건문이 필요하다.
검색어에 해당되는 영화들 중 이미지가 있고, 이미지 타입이 사람이 아니어야 한다. (저 검색 API에서 영화만 받으려는 것이다.)
movieImageUrl은 이전에 이미지를 가져올 때 기본 url + 이미지 사이즈 + 이미지 주소를 입력하면 된다고 했다.
(
<section className="no-results">
<div className="no-results__text">
<p>찾는 검색어 "{searchTerm}"에 맞는 영화가 없습니다.</p>
</div>
</section>
);
};
다음으로 없으면 p 태그로 니가 찾는 거 없다 라고 알려주면 된다.
3. useDebounce Custom Hooks 만들기
📌 Debounce
특정 이벤트나 함수의 호출을 지연시키는 기술로 사용자 입력이나 이벤트 핸들러 같이 빠른 연속 호출을 제어할 때 사용된다.
일정 시간동안 연속된 호출을 감지하고, 마지막 호출 이후에 일정 시간이 지날 때까지 기다린 후 다음 이벤트나 함수를 실행한다.
예를 들어서 지금 검색어를 입력할 때마다 계속 검색 결과를 가져오라고 API를 호출하고 있는데, 이렇게 되면 불필요한 네트워크 트래픽과 서버 부하가 발생할 수 있다고 한다. 그래서 너무 와다다다 입력하는대로 가져오지 말고 약간 텀을 두고 진짜 검색어에 해당되는 API를 호출하게 하는 것이다.
이를 위해 hook 폴더를 만들고 useDebounce.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;
};
useDebounce 는 주어진 value 값에 대해 debounce된 값을 반환한다.
파라미터로 들어온 value 는 debounce를 적용할 값이고, delay는 debounce의 지연 시간 (밀리초)를 뜨한다.
먼저 const [debounceValue, setdebounceValue] = useState(value)를 초기값으로 사용한다.
useEffect를 써서 value나 delay가 변경될 때마다 debounceValue를 업데이트 한다.
setTimeout을 써서 delay 시간 이후에 setdebounceValue를 호출해서 값을 업데이트 해준다.
useEffect의 반환 함수에는 이전에 설정된 타이머를 해제해주는 clearTimeout을 써준다.
index.js에서도 이 부분을 아래와 같이 수정해준다.
const debouncedSearchTerm = useDebounce(searchTerm, 500);
useEffect(() => {
if (debouncedSearchTerm) {
fetchSearchMovie(debouncedSearchTerm);
}
}, [debouncedSearchTerm]); //의존 배열에 넣은 이유는 검색어를 입력하는 동안에도 해당되는 것들을 보여주게 하기 위함
어렵다...
'⚛️ React > 💭 따라하며 배우는 React A-Z' 카테고리의 다른 글
[React] TDD (Test Driven Development) (0) | 2023.07.14 |
---|---|
[React] 넷플릭스 웹페이지에 모달과 슬라이드를 업데이트하자 (feat. 따라하며 배우는 리액트 A-Z) (0) | 2023.06.02 |
[React] React-Router-DOM과 API에 대해 알아보자 (feat. 따라하며 배우는 리액트 A-Z) (0) | 2023.06.02 |
[React] 넷플릭스 웹페이지의 모달을 구현해보자 (feat. 따라하며 배우는 리액트 A-Z) (0) | 2023.06.02 |
[React] 넷플릭스 웹페이지의 영화를 나열하고 footer를 만들자 (feat. 따라하며 배우는 리액트 A-Z) (0) | 2023.06.02 |