본문 바로가기
TIL

11. 13. 26일차 TIL React 두번째 과제_마무리

by 눈 새 2024. 11. 13.

오늘은 예비군을 갔다와서 기절한 후 아침에 일어난 미룬이는 .. 드디어 두번째 React과제를 마무리했다.

 

오늘한 작업

  • 추가기능
  • 삭제기능
  • 디테일 페이지 구현
  • github 생성 및 브랜치 옮기기
  • context 사용하여 상태 전역 관리하기
  • vercel 배포하기

1. 추가 기능

const [selectedPokemon, setSelectedPokemon] = useState([]);
  
  // 포켓몬 선택 추가
  const handleSelectPokemon = (e) => {    

    if (selectedPokemon.length >= 6) {
      alert("포켓몬은 최대 6마리까지만 등록할 수 있습니다.");
      return;
    }
    const pokemonName = e.target.id;

    const newList = mockData.find(
      (pokemon) => pokemon.korean_name === pokemonName
    );

    if (!newList) {
      alert("존재하지 않는 포켓몬입니다.");
      return;
    }

    const somePokemon = selectedPokemon.some(
      (item) => item.korean_name === pokemonName
    );

    if (somePokemon) {
      alert("이미 등록된 포켓몬입니다.");
      return;
    }

    setSelectedPokemon([...selectedPokemon, newList]);
  };

 

포켓몬카드에 선택버튼을 클릭하면 대시보드에 있는 포켓볼에 선택한 포켓몬이 들어간다.


2. 삭제기능

// 포켓몬 선택 해제 및 알림
  const handleDeletePokemon = (e) => {

    const pokemonName = e.target.id;

    const filteringPokemon = selectedPokemon.filter(
      (list) => list.korean_name !== pokemonName
    );
    
    setSelectedPokemon(filteringPokemon);
    alert(`선택한 ${pokemonName}을 취소합니다.`);
  };

  // 선택 포켓몬 초기화
  const handleResetPokemon = (e) => {
    e.preventDefault();

    setSelectedPokemon([]);

    alert("선택한 포켓몬을 모두 취소했습니다.")    
  }

 

 

카드에 있는 빨간 버튼을 클릭하면 선택한 포켓몬이 취소 되고, 타이틀 아래 초기화 버튼을 누르면 선택된 모든 포켓몬이 취소된다.


3. 디테일 페이지 구현

import { useState, useEffect } from "react";
import { useNavigate, useParams } from "react-router-dom";
import { mockData } from "../assets/data/mockData";
import { StyledDetailWrap } from "../styledComponent/DetailsStyles/StyledDetailWrap";

const Details = () => {
  const { id } = useParams();
  const [pokemon, setPokemon] = useState(null);

  useEffect(() => {
    const findPokemon = mockData.find((item) => item.id === Number(id));

    // console.log(findPokemon);

    setPokemon(findPokemon);
  }, [id]);

  const navigate = useNavigate();
  const goToBackPage = () => {
    navigate(-1);
  };

  return (
    <div>
      {pokemon && (
        <StyledDetailWrap>
          <img src={pokemon.img_url} alt={pokemon.korean_name}></img>
          <h3>{pokemon.korean_name}</h3>
          <p>{pokemon.types}</p>
          <span>No. {String(pokemon.id).padStart(3, "0")}</span>
          <p>{pokemon.description}</p>
          <button type="button" onClick={goToBackPage}>
            도감페이지로 돌아가기
          </button>
        </StyledDetailWrap>
      )}
    </div>
  );
};

export default Details;

 

아래는 구현된 페이지의 모습이다. 


4. github 브랜치 생성 및 브랜치 옮기기

1) git branch (새로운브랜치이름)

git branch props-drilling

 

어떤 브랜치가 생성되었는지 확인하는 방법은 터미널에 git branch를 입력하면 된다.

main에 있는 파일을 props-drilling 브랜치로 옮긴 후 context AIP로 전역 상태 관리를 할 예정이다.

 

 

2) git switch (원하는 브랜치 이름)

git switch props-drilling

 

git switch를 입력하여 원하는 브랜치로 이동한다. 이동하면 git branch입력 시 내 위치가 초록색으로 표시된다.

 

3) git add. / git commit -m

원하는 브랜치로 이동한 것을 확인 후 모든 파일을 추가하고 commit한다.

git add .
git commit -m "📝props-drilling 브랜치로 파일 옮기기"

 

4) git push

마지막으로 push해준다.

git push origin 브랜치이름

5. Context API를 사용하여 상태 전역관리하기

1) 폴더 생성

context 관련 파일을 관리할 폴더를 src폴더 안에 생성한다.

 

2) MyContext.jsx 파일을 작성한다.

import React, { createContext } from 'react'

export const MyContext = createContext();

 

3) MyProvider.jsx 파일의 기본 구조를 작성한다.

import { MyContext } from "./MyContext";

const MyProvider = ({ children }) => {
   
  return (
    <MyContext.Provider value={{ }}>
      {children}
    </MyContext.Provider>
  );
};

export default MyProvider;

 

4) 전역에서 관리해야 하는 props를 찾아 MyProvider 파일에 넣는다.

import { useState } from "react";
import { MyContext } from "./MyContext";
import { mockData } from "../assets/data/mockData";

const MyProvider = ({ children }) => {  

  const [selectedPokemon, setSelectedPokemon] = useState([]);
  
  // 포켓몬 선택 추가
  const handleSelectPokemon = (e) => {    

    if (selectedPokemon.length >= 6) {
      alert("포켓몬은 최대 6마리까지만 등록할 수 있습니다.");
      return;
    }
    const pokemonName = e.target.id;

    const newList = mockData.find(
      (pokemon) => pokemon.korean_name === pokemonName
    );

    if (!newList) {
      alert("존재하지 않는 포켓몬입니다.");
      return;
    }

    const somePokemon = selectedPokemon.some(
      (item) => item.korean_name === pokemonName
    );

    if (somePokemon) {
      alert("이미 등록된 포켓몬입니다.");
      return;
    }

    setSelectedPokemon([...selectedPokemon, newList]);
  };

  // 포켓몬 선택 해제 및 알림
  const handleDeletePokemon = (e) => {

    const pokemonName = e.target.id;

    const filteringPokemon = selectedPokemon.filter(
      (list) => list.korean_name !== pokemonName
    );
    
    setSelectedPokemon(filteringPokemon);
    alert(`${pokemonName}을(를) 취소합니다.`);
  };

  // 선택 포켓몬 초기화
  const handleResetPokemon = () => {

    setSelectedPokemon([]);

    alert("선택한 포켓몬을 모두 취소했습니다.")    
  }

  return (
    <MyContext.Provider value={{ selectedPokemon, handleSelectPokemon, handleDeletePokemon, handleResetPokemon, mockData }}>
      {children}
    </MyContext.Provider>
  );
};

export default MyProvider;

 

5) Router를 MyProvider로 감싼다.

import MyProvider from "./context/MyProvider";
import Router from "./shared/Router";

const App = () => {
  return (
    <MyProvider>
      <Router />
    </MyProvider>
  );
};

export default App;

 

6) 각 Component에서 사용되는 props를 연결한다.

...
import { MyContext } from "../context/MyContext";
import { useContext } from "react";

const PokemonCard = ({ data }) => {

  const { handleSelectPokemon } = useContext(MyContext);
  
  ...

  return (
    <StyledPokemonCard key={data.korean_name}>
     
     ...
     
      <button type="button" id={data.korean_name} onClick={handleSelectPokemon}>
        선택
      </button>
      
    </StyledPokemonCard>
  );
};

export default PokemonCard;
...
import { MyContext } from "../context/MyContext";
import { useContext } from "react";

const Dashboard = () => {

  const {selectedPokemon, handleDeletePokemon, handleResetPokemon} = useContext(MyContext);

  return (
    <StyledDashboard>
      ...
      <StyledResetBtn onClick={handleResetPokemon}>초기화버튼</StyledResetBtn>
      
      <StyledMyPokemonWrap>
        {Array.from({ length: 6 }).map((_, index) => {
          const currentPokemon = selectedPokemon[index];

          return (
            ...
                  <button
                    type="button"
                    className="selected"
                    id={currentPokemon.korean_name}
                    onClick={handleDeletePokemon}
                  >
                    삭제
                  </button>
                </StyledPokemonCard>
             ...
      </StyledMyPokemonWrap>
    </StyledDashboard>
  );
};

export default Dashboard;

7) vercel로 배포하기

vite 프로젝트를 vercel에 배포하기 위해서는 package.json파일과 동일한 경로에 vercel.json 파일이 있어야한다.

경로를 확인 후 알맞은 위치에 파일을 생성해주었다.

// vercel.json

{
  "rewrites": [
    {
      "source": "/(.*)",
      "destination": "/index.html"
    }
  ]
}

 

또한 vercel로 배포를 할 경우 코드컨벤싱에 매우 민감하다. 이번 프로젝트의 경우 components 폴더의 이름이 Components 폴더로 되어 있어 (앞에 철자가 대문자) 배포하는 과정에서 오류가 발생하였다. 튜터님의 도움으로 이를 해결하였고 다시 확인 후 배포를 시도하였다.

 

이렇게 React 두번째 프로젝트가 마무리되었다.


★ 26일차 소감

 

개인적으로 너무 많은 아쉬움이 남는 프로젝트였다. 시간이 부족하지 않다고 생각했고 빨리 필수기능을 구현한 뒤  rtk나 다른 추가 구현사항들을 도전해보고 싶었는데 몇몇 일정과 작업환경의 오류로 필수까지만 구현했다. 다음에 진행할 팀 프로젝트에서는 계획단계부터 꼼꼼하게 구상하고 해보고 싶은 기능은 모두 넣어보고 싶다. (UX&UI를 고려하여 이를 해치지 않는 선에서 말이다.) 다음에는 어떤 페이지를 만들게 될까 벌써 기대가 된다.