React 개인과제 Olympic Medal Tracker 만들기 ①

하영·2024년 8월 13일
1

React

목록 보기
4/17
post-thumbnail

자바스크립트 과제들을 하면서 개발에 조금 친해졌다고 생각했는데 리액트를 만나고 나니까 다시 응애가 되어버렸다...ㅋ
자바스크립트 하듯이 똑같이 생각하고 코드를 짜면 되는데 처음 써보는 문법들이 어색해서 그런건지 어버버 한 상태가 되었고 그 어느때보다 하기싫고 무서운 마음으로 과제를 시작했다.


Olympic Medal Tracker


👩🏻‍💻 구현해야 할 필수기능

  • 제출 폼 UI 구현하기: 나라 이름과 금, 은, 동 메달 수를 입력할 수 있는 폼을 만들기
  • 메달 집계 CRUD 구현하기
    • Create: 새로운 나라와 그 나라가 획득한 메달 수를 추가
    • Read: 나라별 메달 집계 리스트 보여주기
    • Update: 기존에 추가된 나라의 메달 수를 수정하여 리렌더링 하기
    • Delete: 나라 정보를 삭제하기
  • 정렬: 메달 집계는 금메달 수를 기준으로 내림차순 정렬되어야 합니다.

🧐 Hint

  • 사용한 hook은 오직 useState
  • 국가를 추가하는 부분에는 <form> 안에 국가명, 금메달, 은메달, 동메달을 위한 <input>을 각각 만들기
  • 사용한 javascript 내장 메서드는 map, filter , sort
  • initial state: {id: 0, country: "", gold: 0, silver: 0, bronze: 0}

01. 기본 UI 레이아웃 작성하기

가장 먼저 기본 레이아웃을 구성했다.
힌트에서 본 것처럼 form 태그를 사용했고 그 아래에 input, button 태그를 넣어 만들었다.

jsx 작성하기(기본 UI)

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

// 중략

<div className="container">
      <h1>2024 파리 올림픽</h1>
      <form 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>
  );
};

export default App;

⭐️ <input /> 태그를 만들 때 무조건 머리통에 박고 시작해야하는 것 2가지가 있다.

  1. inputstate를 만들기.
  2. input을 만들면 해당 값을 받아올 value와 , 이벤트onChange 적어서 사용하기

inputstate를 만드는 이유는 input창에 값을 입력할 때마다 변화된 값을 리렌더링하고 사용하기 위해서이다.
onChange 이벤트는 사용자가 입력필드에 값을 입력/삭제할때마다 발생하기 때문에 실시간으로 값을 업데이트하거나 다른 처리를 수행할 수 있다.


input 이벤트 작성하기

	// 국가 입력(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);
  };

위 코드에서도 보았지만 input에 필요한 이벤트는 이렇게 따로 자바스크립트 영역에 만들어서 작성했다.
특별한 내용은 없고 그냥 input에 이벤트가 부여되면 해당 input의 value를 받아오는 코드이다.

똑같은게 반복되어서 확 줄이고 싶은데 컴포넌트 분리하면서 같이 정리해봐야겠다.


02. 상태 관리와 메달 집계 추가 기능 구현

그 다음 구현한 것은 state를 만들고 나라와 메달을 입력후 '국가추가' 버튼을 만들면 아래에 추가 되게 만드는 것이었다.

state 만들기

const [countries, setCountries] = useState([]); // 나라 전체를 배열로 담을 state
const [country, setCountry] = useState(""); // 나라 별 state
const [gold, setGold] = useState(0); // 금메달 state
const [silver, setSilver] = useState(0); // 은메달 state
const [bronze, setBronze] = useState(0); // 동메달 state

국가 및 메달 추가 배열 함수

  const addCountryHandler = (e) => {
    e.preventDefault();
    const newCountry = {
      id: new Date().getTime(),
      country: country,
      gold: gold,
      silver: silver,
      bronze: bronze,
    };

	setCountries([...countries, newCountry]);
  };

위에서 만든 4개의 input창에 국가명, 메달 수를 입력하면 기존 countries 배열에 추가한 국가의 리스트가 쭉 들어가는 코드이다.
id는 삭제버튼 구현할 때 사용했는데 중복되지 않는 고유한 값을 주기 위해 new Date().getTime() 속성을 사용했다.

리액트 강의에서도 그렇고 이보다 좀 더 효율적인? 방법이 있는 것 같다.
이렇게 주는 방식이 별로 마음에 안 들어서 기성님😇 도움 받아서 index로 고유 값을 주는 방식으로 수정했었는데 중간피드백 받을 때 지금은 우선 Date() 함수를 사용하라고 해서 다시 수정했다. 이 부분도 질문드리거나 검색해봐야겠다.


03. 메달 개수 업데이트 기능 구현

개인적으로 가장 힘들었고 꼬아서 생각해버리는 바람에 시간이 많이 빼앗긴 부분이다.

//나라 업데이트 함수
  const updateCountryHandler = () => {
    const newCountries = countries.map((item) => {
      if (item.country === country) {
        return { country, gold, silver, bronze };
      } else {
        return item;
      }
    });
  };

코드를 다 쓰고 나니 아주 간단했다.

1) 바꿀 나라의 이름(item)과 기존 나라의 이름(country)가 같은지 확인
2) 같다면 해당 국가의 값을 전부 업데이트해준다.
3) 국가 이름이 같지 않다면 원래 값을 남긴다.
4) map 메소드로 동일한 국가의 리스트를 새로 바꿔서 업데이트 해준다.


🤯 하루 내내 싸웠던.. 잘못된 코드

find 메소드를 사용해보라고 해서 써보려다가 진짜 진짜 먼 길을 돌았다..ㅋㅋㅋ 무려 튜터님께 2번이나 질문드리러 갔던 부분의 결과물이다. 다 만들었을 때 이 단순한 코드도 한참 걸리는 내가 너무 창피했다 으윽🤮🤮🤮


헷갈리지말고 신중하게!!

⭐️ map 메소드를 실행할 때 매개변수로 담는 값이 뭔지를 정확히 알아야 할 필요가 있다고 생각했다. 아예 새로 생성된 나라라고 생각해서 newCountry라고 이름을 지어버렸는데 원래 있던 나라에서 변경된 값만! 바꾸는거다.
결과적으로 전부 바뀌어서 짜잔~ 하고 나온게 완성 코드에서 변수로 담은 newCountry가 되는거고 내가 map 메소드로 할 일은 나라의 데이터 값을 변경하는거다.


04. 삭제 기능 구현

 const deleteCountryhandler = (id) => {
    const deleteCountry = countries.filter((country) => {
      return country.id !== id;
    });
    setCountries(deleteCountry);
  };

삭제는 id 값 가져오는 부분에서 살짝 헤맸던거 말고는 비교적 간단하게 한 것 같다.
filterboolean 을 반환한다는 점을 생각해서 id 값이 클릭한 그! id와 서로 같지 않으면 리스트에 남겨두어서 삭제된 것처럼 만들었다.


05. 금메달 수 기준 정렬하기

// 국가 추가하기 버튼
const goldSorted = [...countries, newCountry].sort(
      (a, b) => b.gold - a.gold
);
  setCountries(goldSorted);
  setCountry("");

// 업데이트 버튼
const goldSorted = newCountries.sort((a, b) => b.gold - a.gold);
    setCountries(goldSorted);
    setCountry("");

정렬하는 함수는 <추가하기> 버튼을 눌렀을 때, <업데이트> 버튼을 눌렀을 때에 코드를 넣었다. 정렬하는 함수만 따로 빼서 만들어보고 싶었는데 완벽하게 똑같지는 않아서 우선은 각각 코드를 작성했다.

불변성을 유지하기 위해서 추가하기 버튼 쪽에는 spread Operator를 사용했지만 업데이트 부분에는 애초에 새로 값을 수정해서 뿌려주는 방식이라 기존 데이터를 신경쓰지 않아도 되었다.

이렇게 나라를 추가하면 금메달 수를 기준으로 정렬이 되는 걸 확인할 수 있다!

1차 피드백을 받은 후 금메달 말고 전체 개수로 정렬하는 것까지 만들어보고 싶은데 그렇게 되면 아마 <삭제하기> 버튼에도 이 로직이 필요할 것 같다...


👩🏻‍💻 전체 코드

import { useState } from "react";
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 = (e) => {
    e.preventDefault();
    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
    );
    // console.log(goldSorted);
    // setCountries([...countries, newCountry]);
    setCountries(goldSorted);
  };

  // 국가 입력(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 deleteCountry = countries.filter((country) => {
      return country.id !== id;
    });
    setCountries(deleteCountry);
  };

  //나라 업데이트 함수
  const updateCountryHandler = (e) => {
    e.preventDefault();
    const newCountries = countries.map((item) => {
      if (item.country === country) {
        return { country, gold, silver, bronze };
      } else {
        return item;
      }
    });
    const goldSorted = newCountries.sort((a, b) => b.gold - a.gold);
    setCountries(goldSorted);
  };

  return (
    <div className="container">
      <h1>2024 파리 올림픽</h1>
      <form 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>
        {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>
    </div>
  );
};

export default App;

// 버튼 컴포넌트 시키기
const Button = ({ children, onClick, color }) => {
  if (color) {
    return (
      <button
        style={{
          backgroundColor: color,
        }}
        onClick={onClick}
      >
        {children}
      </button>
    );
  }
  return <button onClick={onClick}>{children}</button>;
};

1차적인 필수기능구현은 끝나고 추가기능을 넣을까 하다가
나도 컴포넌트를 나눠서 조금이라도 보기 좋은 코드로 만들어보고 싶어서 컴포넌트를 나눠보고 있다. 너무 날것의 jsx문법이 덕지덕지 되어있는 것 같아서...ㅎ 리액트 첫 과제라는 걸 티내보고자 이것저것 리팩토링하고 있다..!

지금도 불필요한 코드도 삭제하고 조금씩 수정하고 있는데 리팩토링 부분은 과제제출하고 싹 정리해서 마무리 지어보려고 한다!

👩🏻‍💻 앞으로 보완할 점

  • button 속성 부여와 form 태그에 onSubmit 이벤트 넣기
  • country 리스트 추가되는 부분 코드 수정하기
  • 컴포넌트 파일 import, export 만들기
    ++ 튜터님 1차 피드백 건 추가....ㅎ
profile
왕쪼랩 탈출 목표자의 코딩 공부기록

0개의 댓글