포켓몬 도감을 만들어보자 - 2

이기찬·2023년 9월 19일

프로젝트

목록 보기
2/6

무한 스크롤


[포켓몬 요청 API](https://pokeapi.co/api/v2/pokemon/?offset=0&limit=20)

https://pokeapi.co/api/v2/pokemon/?offset=0&limit=20

요청 데이터를 확인 해보면 next 라는 URL 이 있는 것을 확인 할 수 있는데 next 로 넘어가게 되면 offset의 값이 증가하고 다음 목록의 포켓몬을 20 개 반환 하는 것을 알 수 있습니다.

포켓몬을 가져올 때 offset 과 limit 의 값을 설정 하여 자신이 받아오고 싶은 만큼의 포켓몬을 요청하여 결과 값을 받아올 수 있습니다.

이전에 보여드린 getAllPokemonAPI 라는 api 요청 function 은 다른 function 들과 다르게 매개 변수로 offset 을 받아오는데, 스크롤을 화면의 끝까지 내렸을 때 "react-intersection-observer" 가 이것을 감지하여 offset 의 값을 증가 시키기 위함 입니다.

function Pokemon() {
  const LIMIT_PER_PAGE = 20; // 요청 URL의 offset 증가를 위한 상수
  
  // 포켓몬을 담기 위한 빈 배열 선언
  const [pokemonList , setPokemonList] = useState([]);
  const [page, setPage] = useState(0); 
  const [ref, inView] = useInView(true);
  
  useEffect(() => {
    async function fetchData() {
      try {
      	
        // offset은 프로그램 시작 후 증가 -> -20 에서 0 으로 변경.
        const offset = (page - 1) * LIMIT_PER_PAGE;
        
        // getAllPokemonAPI 에 offset 전달.
        const pokemonData = await getAllPokemonAPI(offset);
        
        // 기존 데이터와 새로운 데이터가 병합 된 newList 반환.
        setPokemonList((prevList) => {
          const newList = [...prevList, ...pokemonData]; 
          return newList; 
        });
  
		} catch (error) {
        console.error('Error:', error);
      }
    }
	
    // fetchData() 호출.
    fetchData();

	// dependency 를 page 로 설정 -> page 가 변경 될 때마다 실행.
  }, [page]);  
  
  // 페이지 로드시 uploadData 함수 호출
  useEffect(() => {
    const uploadData = () => {
      
      // inView === true  page 이전 값에서의 증가.
      if (inView) {
        setPage((prevPage) => prevPage + 1);
      }
    };
    
    // uploadData() 호출
    uploadData();
    
    // inView 가 감지되면 uploadData() 호출
  }, [inView]);
  
  return(
  	<div>
       {pokemonList.map((pokemon, index) => (
            // pokemonModal = 디테일한 정보 전달
            <PokemonCard
              key = {index}
              pokemon = {pokemon}
              .
              .
              .
            />
        ))}
    </div>
  );
}

export default Pokemon;

스크롤을 내릴 때마다 API 를 요청하고, 요청된 추가적인 정보를 화면에 출력하기 위한 코드입니다. (코드가 길고 복잡한 점은 개선해나가겠습니다..!)




포켓몬코리아(pokedex)

포켓몬스터 공식 홈페이지의 도감 목록 입니다.기본적으로 포켓몬을 카드의 형태로 출력하고, 카드를 클릭 하였을 때 포켓몬의 디테일한 정보를 보여줍니다.


좋은 방식이라고 생각 했으나, 그대로 만드는 것 보다 다른 방식으로 하는 것이 공부가 될 것같아 Modal 방식을 사용해 보았습니다.

직접 Modal 창을 만들어 보는것과 라이브러리를 사용하는 것을 고민하다가,
라이브러리를 사용해보았습니다.


flowbite-react

npm i flowbite-react

이것저것 보다가 찾은 flowbite-react 에 대한 Docs 입니다. 설명을 보면

Flowbite React는 핵심 Flowbite 구성 요소를 기반으로 하고 React 구성 요소와 상호 작용 처리 기능으로 구축된 무료 오픈 소스 UI 구성 요소 라이브러리입니다.

이 라이브러리에는 Navbar, 드롭다운, 모달 및 사이드바와 같은 수백 개의 대화형 요소가 포함되어 있으며 모두 React에서 처리하고 Tailwind CSS의 유틸리티 클래스를 기반으로 합니다.

tailwind를 사용하고 Modal 창을 구현하기 위한 저에게 꼭 맞다고 생각하여 flowbite-react 를 사용해보았습니다.


Docs 에 따라 tailwind.config 를 설정 해주신 후

flowbite-Modal 문서에서 Modal에 대한 내용을 본 뒤 프로젝트에 어울리는 것이 무엇인지 판단하고 사용하시면 될 것 같습니다.


Backdrop dismissible Modal은 창을 띄운 후 Modal이 아닌 바깥 부분을 클릭하면 Modal 창이 닫히게 됩니다. 기본적인 Default Modal 방식 보다 사용자에게 좋은 경험을 줄 수 있을 것 같아 채택하였습니다.


기본 방식과 다른 점은

props.setOpenModal('dismissible')

<Modal dismissible show={props.openModal === 'dismissible'}/>

props.setOpenModal()을 dismissible 로 변경, Modal 컴포넌트의 props 에 dismissible 을 추가 해주시면 됩니다.


또한 Modal 창을 클릭 하였을 때 뒷 배경의 Scroll 을 막기 위해 코드를 추가 하였습니다.

const [openModal, setOpenModal] = useState();

useEffect(() => {
    if (openModal) {
      // 모달이 열렸을 때 body에 오버플로우 숨김 스타일 추가
      document.body.style.overflow = "hidden";
    } else {
      // 모달이 닫혔을 때 body 스타일 초기화
      document.body.style.overflow = "auto";
    }
  }, [openModal]);

또한 조건부 렌더링을 사용하여 Modal 이 열린 상태라면 뒷 배경이 어두워지게 작성 하였습니다.

const [openModal, setOpenModal] = useState();

{openModal && (
	<div 
      className="fixed inset-0
      z-50 flex items-center
      justify-center 
      bg-black bg-opacity-70
    ">
    	{pokemonDetail}
    </div>
)}

결과물


openModal

profile
안녕하세요

0개의 댓글