React 개인과제 Olympic Medal Tracker 만들기 ② - 리팩토링...?

하영·2024년 8월 14일
1

React

목록 보기
5/17

어제에 이어서 후딱 써보는 개인과제 회고..!
튜터님 피드백 받은걸 토대로 수정하고 쓰고 싶었는데 언제 피드백이 올지 몰라서 우선 중간중간 바꾸고 수정한 부분만 정리해보기로 ~,~


Olympic Medal Tracker

👩🏻‍💻 보완해야할 부분

  • 버튼 클릭 시 경고/확인창 추가
    • 업데이트 클릭 시 중복 국가 없을 경우 경고창 띄우기
    • 국가명 input창 공백 시 경고창 띄우기
    • 삭제버튼 누르면 confirm 창 띄우기
  • input 문자열을 숫자로 변환시키기
  • 컴포넌트 나누기
  • module.css 연결 마무리
  • country 리스트 추가되는 부분 코드 수정하기

01. 버튼 클릭 시 경고/확인창 추가

🚧 국가추가 버튼

국가명을 입력해야하는데 국가명 input을 공백으로 두었을 경우 입력해달라는 경고창을 추가했다.

const addCountryHandler = () => {
    if (!country) {
      alert("국가명을 입력해주세요");
      return; //early return
    }

    const newCountry = {
      id: new Date().getTime(),
      country: country,
      gold: gold,
      silver: silver,
      bronze: bronze,
    };
    const goldSorted = [...countries, newCountry].sort(
      (a, b) => b.gold - a.gold
    );
    setCountries(goldSorted);
    setCountry("");
  };

조건문으로 해결했는데 country가 없다면 "국가명을 입력해주세요" 라는 alert창이 뜨고 return을 써줌으로써 early return을 해주었다.


🔍 early return?

말 그대로 빠르게 실행을 종료시키는 것이다. 조건이 부합하지 않으면 곧바로 return을 하도록 하는 코딩 패턴인데 이렇게 작성함으로써, 가독성이 좋은 코드가 될 수 있고 이후에 작성하는 코드에도 영향을 주지 않아 안전한 코딩이 된다.


🚧 업데이트 버튼

const updateCountryHandler = () => {
    const newCountries = countries.map((item) => {
      if (item.country === country) {
        return { id: item.id, country, gold, silver, bronze };
      } else {
        alert("업데이트 할 국가가 없습니다.");
        return item;
      }
    });
    const goldSorted = newCountries.sort((a, b) => b.gold - a.gold);
    setCountries(goldSorted);
    setCountry("");
  };

업데이트 버튼에도 원래 있던 조건문에 경고창만 추가해서 간단하게 끝냈다.


🚧 삭제하기 버튼

CRUD에서 가장 신중해야하는게 delete인데 삭제하기 전에 사용자에게 진짜로 삭제하려고 버튼을 누른게 맞는지 한번 더 확인해주는게 필요하다.

const deleteCountryhandler = (id) => {
    const deleteCheckAlert = confirm("해당 국가를 삭제하시겠습니까?");
    if (deleteCheckAlert === true) {
      alert("삭제되었습니다.");
      const deleteCountry = countries.filter((country) => {
        return country.id !== id;
      });
      setCountries(deleteCountry);
    } else {
      return false;
    }
  };

그래서 삭제하기 전에 confirm을 띄워서 확인을 눌렀을 때 삭제되도록 만들었다.
확인을 누르면 true가 되고 취소를 누르면 false가 되는 부분을 활용해서 이 역시도 조건문을 통해 처리해주었다.

결과확인

(짤이 왜이리 헐었담;;;)


02. input 문자열을 숫자로 변환시키기

이건 스탠다드반 강의를 듣다가 꿀팁?을 알아서 바아로 수정해주었다.
input은 항상 string타입 즉, 문자열을 반환하는데 메달의 개수는 숫자로 나와야한다. 따라서 이걸 숫자형태로 바꿔주어야했다.

//메달 개수 바꾸는 함수
  const goldMedalCounter = (e) => {
    setGold(+e.target.value);
  };
  const silverMedalCounter = (e) => {
    setSilver(+e.target.value);
  };
  const bronzeMedalCounter = (e) => {
    setBronze(+e.target.value);
  };

이렇게 e.target.value 앞에 + 를 붙여주면 숫자형태로 바뀐다!!

결과확인

나라 빼고 나머지는 전부 문자열로 출력된 걸 볼 수 있다. 넘 신기하자노~


03. 컴포넌트 나누기 / module.css 처리하기

이건 컴포넌트 나누면서 css도 따로 수정했기 때문에 같이 정리하면 보기 좋을 것 같다.

우선 컴포넌트를 2개로 나눴는데 하나는 Button 컴포넌트, 다른 하나는 나라 목록이 뜨는 부분의 CountryItem 컴포넌트이다.

폴더 구조


src폴더 아래에 components 폴더를 만든 후 그 안에 각 컴포넌트파일과 css 파일을 새로 만들었다.

🚧 Button 컴포넌트 / css

import "./Button.module.css";

const Button = ({ children, onClick, color }) => {
  if (color) {
    return (
      <button
        style={{
          backgroundColor: color,
        }}
        onClick={onClick}
      >
        {children}
      </button>
    );
  }
  return <button onClick={onClick}>{children}</button>;
};

export default Button;

부모컴포넌트에서 받아올 props를 넘겨주고 default로 내보내주었다.
또 버튼부분만 적용될 module.css 파일도 이 jsx 파일에 import했다.

🚧 CountryItem 컴포넌트 / css

import Button from "./Button";
import style from "./CountryItem.module.css";

const CountryItem = ({ countryItem, deleteCountryhandler }) => {
  const { id, country, gold, silver, bronze } = countryItem;

  return (
    <li className={`${style.medalResultList}`}>
      <p>{country}</p>
      <p>{gold}</p>
      <p>{silver}</p>
      <p>{bronze}</p>
      <Button
        type="button"
        color="#ff6f4b"
        onClick={() => deleteCountryhandler(id)}
      >
        삭제
      </Button>
    </li>
  );
};

export default CountryItem;

동일한 방식으로 CountryItem 컴포넌트와 css파일을 연결해주었다.

💡 처음에 return 안에 있는 li태그에도 key 값을 줬었는데 이미 App.jsx 파일에서 li를 감싸는 ul태그에 key 값을 주었으므로 여기에서는 중복해서 줄 필요가 없다.

App.jsx 코드

<div>
        <ul className="medal-result">
          {countries.map((countryItem) => (
            <CountryItem
              key={countryItem.id}  
             /*key값 미리 부여했으므로 해당 컴포넌트 li에 또 줄 필요 없음!*/           
              countryItem={countryItem}
              deleteCountryhandler={deleteCountryhandler}
            />
          ))}
        </ul>
      </div>

04. country 리스트 추가되는 부분 코드 수정하기

마지막은 나라 목록이 뜨는 부분의 map메소드 도는 구간을 살짝 손 보았다.
map메소드로 반복되는건 li태그 뿐인데 다시 보니까 ul태그 전체가 반복해서 생성되고 있었다ㅋㅋㅋㅋ


원래 코드

<div>
        {countries.map((country) => (
          <ul key={country.id} className="medal-result">
            <li className="medal-result-list">
              <p>{country.country}</p>
              <p>{country.gold}</p>
              <p>{country.silver}</p>
              <p>{country.bronze}</p>
              <Button
                type="button"
                color="#ff6f4b"
                onClick={() => deleteCountryhandler(country.id)}
              >
                삭제
              </Button>
            </li>
          </ul>
        ))}
      </div>

컴포넌트도 안 만들고 저렇게 냅다 ul만 계속 생성되게 했던.... 코드를 아래처럼 아주 살짝 정리했다.


수정한 코드

<div>
        <ul className="medal-result">
          {countries.map((countryItem) => (
            <CountryItem
              key={countryItem.id}
              countryItem={countryItem}
              deleteCountryhandler={deleteCountryhandler}
            />
          ))}
        </ul>
      </div>

그리고 원래는 컴포넌트 이름도 CountryList라고 지었는데 사실 나라가 하나씩 나오면서 리스트로 보여지는거기 때문에 CountryItem이 조금 더 맞는 표현이라는 피드백을 받아서 수정했다.
협업이 중요한 직업이니만큼 남들도 변수명을 보자마자 바로 이해할 수 있는 ✨시멘틱한 이름✨을 짓는 습관을 가지자!!


👩🏻‍💻 최종 App.jsx 파일 코드

딱히.. 큰 변화는 없지만..ㅎㅎ

import { useState } from "react";
import Button from "./components/Button";
import CountryItem from "./components/CountryItem";
import "../src/App.css";

const App = () => {
  const [countries, setCountries] = useState([]);
  const [country, setCountry] = useState("");
  const [gold, setGold] = useState(0);
  const [silver, setSilver] = useState(0);
  const [bronze, setBronze] = useState(0);

  //국가 및 메달 추가 배열 함수(button)
  const addCountryHandler = () => {
    if (!country) {
      alert("국가명을 입력해주세요");
      return;
    }

    const newCountry = {
      id: new Date().getTime(),
      country: country,
      gold: gold,
      silver: silver,
      bronze: bronze,
    };
    const goldSorted = [...countries, newCountry].sort(
      (a, b) => b.gold - a.gold
    );
    setCountries(goldSorted);
    setCountry("");
  };

  // 국가 입력(input) 함수
  const changeCountryHandler = (e) => {
    setCountry(e.target.value);
  };

  //메달 개수 바꾸는 함수
  const goldMedalCounter = (e) => {
    setGold(+e.target.value);
  };
  const silverMedalCounter = (e) => {
    setSilver(+e.target.value);
  };
  const bronzeMedalCounter = (e) => {
    setBronze(+e.target.value);
  };

  // 나라 삭제 함수
  const deleteCountryhandler = (id) => {
    const deleteCheckAlert = confirm("해당 국가를 삭제하시겠습니까?");
    if (deleteCheckAlert === true) {
      alert("삭제되었습니다.");
      const deleteCountry = countries.filter((country) => {
        return country.id !== id;
      });
      setCountries(deleteCountry);
    } else {
      return false;
    }
  };

  //나라 업데이트 함수
  const updateCountryHandler = () => {
    const newCountries = countries.map((item) => {
      if (item.country === country) {
        return { id: item.id, country, gold, silver, bronze };
      } else {
        alert("업데이트 할 국가가 없습니다.");
        return item;
      }
    });
    const goldSorted = newCountries.sort((a, b) => b.gold - a.gold);
    setCountries(goldSorted);
    setCountry("");
  };

  return (
    <div className="container">
      <h1>2024 파리 올림픽</h1>
      <form
        onSubmit={(e) => {
          e.preventDefault();
        }}
        className="form-list"
      >
        <ul className="list-box">
          <li className="list">
            <h3>국가명</h3>
            <input
              type="text"
              value={country}
              onChange={changeCountryHandler}
              placeholder="나라명을 입력하세요."
            />
          </li>
          <li>
            <h3>금메달</h3>
            <input type="number" value={gold} onChange={goldMedalCounter} />
          </li>
          <li>
            <h3>은메달</h3>
            <input type="number" value={silver} onChange={silverMedalCounter} />
          </li>
          <li>
            <h3>동메달</h3>
            <input type="number" value={bronze} onChange={bronzeMedalCounter} />
          </li>
          <Button type="button" onClick={addCountryHandler}>
            국가추가
          </Button>
          <Button type="button" onClick={updateCountryHandler}>
            업데이트
          </Button>
        </ul>
      </form>

      <div>
        <ul className="medal-result">
          {countries.map((countryItem) => (
            <CountryItem
              key={countryItem.id}
              countryItem={countryItem}
              deleteCountryhandler={deleteCountryhandler}
            />
          ))}
        </ul>
      </div>
    </div>
  );
};

export default App;

🤯 개인프로젝트 느낀점

실력에 비해 욕심도 많고 남들하고 비교하는 버릇이 있어서 스스로 괴로운 일주일을 보냈다. 기본에 충실하자는 마음으로 시작했는데 정작 다른 사람들의 코드를 보니 아쉬운 기분은 어쩔 수 없었다.
일주일만에 리액트를 배우고 내꺼로 만들기에는 부족한 시간이었지만 그래도 강의만 듣는게 아니라 뭔가를 만들어봤다는거에 만족해보려한다.
포기하지 않고 제출한 나 칭찬해!!

아직 props로 넘겨주고 컴포넌트를 자유자재로 나눠서 관리하는게 부족하고 아 사실 리액트 자체가 어색하고 덜친해졌다. 숙련주차 들어가기 전에 기존 코드 복습하고 어려운 부분은 꼭 정리해서 이해해야겠다.

profile
왕쪼랩 탈출 목표자의 코딩 공부기록

0개의 댓글