리액트 입문주차 개인과제 (Olympic Medal Tracker 만들기)
[React 6기] 입문주차 개인과제
Goal: Olympic Medal tracker 만들기
필수 구현 요약
- 제출 폼 UI 구현하기: 나라 이름, 메달 수 입력하는 폼
- 메달 집계 CRUD 구현
도전과제
- 동일 나라 추가, 저장되지 않은 국가 업데이트 시, alert 메시지
- 금메달 순으로 정렬, 획득한 메달 수 정렬
- localStorage에 저장
일단 시간 관계 상 필수 구현 완료, 도전과제는 1번만 완료하여 제출하였다.
배포 링크 : https://olympic-medal-tracker-mu.vercel.app/
github 주소 : https://github.com/JongHoJang/olympic-medal-tracker
구현한 코드 및 정리
App.jsx
state로는 국가와 메달 수를 담고 있는 배열만을 유지시키고 나머지는 다 컴포넌트화 시켜서 나눠서 작업
InputForm.jsx
- 나라, 메달 수 등을 input으로 받고, 추가와 업데이트 버튼이 있는 컴포넌트
Table.jsx
- state에 저장된 정보들을 보여주고 삭제버튼이 있는 컴포넌트
App.jsx
// App.jsx
function App() {
// 국가와 메달 수를 담고 있는 객체 배열
const [countries, setCountries] = useState([]);
return (
<div>
<div className="container">
<h1 className="title">2024 파리 올림픽</h1>
<InputForm countries={countries} setCountries={setCountries} />
{/* 메달 통계 부분 > 조건부 렌더링 */}
{countries.length === 0 ? (
<h3> 아직 메달을 획득한 나라가 없습니다. </h3>
) : (
<Table countries={countries} setCountries={setCountries} />
)}
</div>
</div>
);
}
export default App;
추가된 국가가 없는 경우 국가 리스트가 제공되는 부분에 "아직 메달을 획득한 나라가 없습니다." 문구가 나오고,
국가가 추가된 시점부터 국가 리스트가 보였으면 좋겠다는 니즈가 있었다.
if문을 써야하나? 삼항 연산자를 써야하나? 고민하고 있던 순간 튜터님께서 조건부 렌더링을 찾아보라고 힌트를 주셨다.
조건부 렌더링 (Conditional Rendering)
특정 값에 따라서 다른 컴포넌트를 보여주고 싶을 때 사용하는 방법.
여기에는 여러 가지의 방식이 있다고 한다.
그 중 가장 많이 사용하는 삼항연산자 방식이 있었다.
{countries.length === 0 ? (
<h3> 아직 메달을 획득한 나라가 없습니다. </h3>
) : (
<Table countries={countries} setCountries={setCountries} />
)}
countries 배열의 길이가 0인 경우 <h3> 문구를 보여주고, 그렇지 않은 경우 <Table /> 컴포넌트를 보여줘라
InputForm.jsx
처음으로 input에 적는 내용들을 받아주고 변경해주는 state가 필요했다
const [countryInfo, setCountryInfo] = useState({
name: "",
gold: 0,
silver: 0,
bronze: 0
});
onChange의 name 속성
그리고 input에 onChange를 줘서 변화하는 값을 체크하고 state에 저장해주는 코드를 작성했다.
const onInputChange = (e) => {
const { name, value } = e.target;
setCountryInfo({ ...countryInfo, [name]: value, id: new Date().getTime() });
};
// ...
return (
<>
<div className="input-container">
<form onSubmit={addCountry}>
<div className="input-item">
<label>국가명</label>
<input type="text" placeholder="국가 입력" value={countryInfo.name} name="name" onChange={onInputChange} />
</div>
<div className="input-item">
<label>금메달</label>
<input type="number" value={countryInfo.gold} name="gold" onChange={onInputChange} />
</div>
// ...
이 코드를 정리하자면
name은 input 요소의 속성값으로 key와 같은 역할을 한다. value가 input 요소에 입력된 실제 값!!
즉, onChange 이벤트에서 사용하는 name과 실제 input 요소에 있는 name 속성이 동일하면 state 객체의 name 속성에 저장이 되는 것이다.
이 name 속성을 몰라서 처음에 고생을 좀 했다..ㅠㅠ
중복되는 국가명 찾기 find()
const searchCountry = countries.find(({ name }) => name === countryInfo.name);
find 함수를 통해 중복되는 국가명을 찾아줬다.
countries에 name 속성값을 받아와 input에 입력된 countryInfo의 name과 비교하게 된다.
그리고 searchCountry 변수는 추가와 업데이트 때 계속 사용하게 된다.
추가 버튼
const addCountry = (e) => {
e.preventDefault();
if (countryInfo.name === "") {
alert("국가명을 적어주세요.");
} else if (!searchCountry) {
setCountries([...countries, countryInfo]);
} else {
alert("이미 추가된 나라입니다.");
}
console.log(countries);
// input 초기화
setCountryInfo({
name: "",
gold: 0,
silver: 0,
bronze: 0
});
};
여기는 setCountries에 기존 countries 배열을 복사해 그 뒤로 입력받은 countryInfo를 추가해줬다.
그 후 inputd을 초기화해주었다.
여기서 유효성 검사를 통해 input값이 비어있거나, 겹치는 경우 alert를 띄워주었다.
수정 버튼
const handleUpdateCountry = (e) => {
e.preventDefault();
if (searchCountry) {
searchCountry.gold = countryInfo.gold;
searchCountry.silver = countryInfo.silver;
searchCountry.bronze = countryInfo.bronze;
setCountries([...countries]);
} else if (!searchCountry) {
alert("해당 국가가 없습니다. 국가를 먼저 추가해주세요");
}
};
만약 중복되는 국가명이 있는 경우 각 메달을 업데이트해주는 코드를 작성했다.
참고로 searchCountry는 countries의 값을 find 메서드를 통해 참조를 했기 때문에
searchCountry.gold를 바꿔주면 countries의 gold 값이 바로 바뀌게 된다!
그렇기 때문에 마지막에 ...countries를 다시 불러와주면 다시 렌더링이되어 UI가 변경된다!
Table.jsx
먼저 금메달 개수를 기준으로 오름차순을 해주기 위해 변수를 만들어줬다.
const sortedCountries = [...countries].sort((a, b) => b.gold - a.gold);
테이블 만들기
그 후 map을 돌려 table에 추가하기!
{sortedCountries.map((country) => (
<tr key={country.id}>
<td>{country.name}</td>
<td>{country.gold}</td>
<td>{country.silver}</td>
<td>{country.bronze}</td>
<td>
<button onClick={() => handleDeleteCountry(country.id)}>삭제</button>
</td>
</tr>
))}
삭제버튼
const handleDeleteCountry = (id) => {
console.log(id);
const filteredCountry = countries.filter((selected) => {
if (selected.id === id) {
return false;
} else {
return true;
}
});
setCountries(filteredCountry);
};
filter 메서드를 통해 countries 배열을 순회하며 각 국가 객체를 새로운 배열인 filteredCountry로 만들어줬다.
그 후 id를 체크해서 같다면 false를 주어 배열에 포함하지 않았다. 한마디로 삭제 되었다는 뜻!!
마지막으로 countries를 바꿔주는 setCountries에 추가해주면 해당 리스트는 삭제가 된다.