1. 프로젝트 셋업 및 기본 컴포넌트 구성
1-1. 프로젝트 구조

1-2. 페이지 구성 및 컴포넌트 구성
 |  |  |
|---|
| Home.jsx | Dex.jsx | Detail.jsx |
 |
|---|
| 컴포넌트 구성 |
2. 포켓몬 리스트와 선택, 삭제, 알림 기능 구현
2-1. Dex.jsx(후에 Router.jsx로 옮김)
const onAddHandler = (pokemon) => {
const addedPokemon = selectedPokemon.find((p) => {
return p.id === pokemon.id;
});
if (addedPokemon) {
alert("이미 추가된 포켓몬 입니다.");
} else if (selectedPokemon.length >= 6) {
alert("6개 이상의 포켓몬을 담을 수 없습니다.");
} else {
setSeletedPokemon([...selectedPokemon, pokemon]);
}
};
const onDeleteHandler = (id) => {
const newPokemonList = selectedPokemon.filter((p) => p.id !== id);
return setSeletedPokemon([...newPokemonList]);
};
const newMockList = MOCK_DATA.map((pokemon) => {
const selectedId = selectedPokemon.map((p) => p.id);
if (selectedId.includes(pokemon.id)) {
return {
...pokemon,
isSelected: true,
};
} else {
return pokemon;
}
});
2-2. PokemonList.jsx
<StPokemonList>
{newMockList.map((pokemon) => (
<li key={pokemon.id}>
<PokemonCard
pokemon={pokemon}
onAddHandler={onAddHandler}
isSelected={false}
/>
</li>
))}
</StPokemonList>
2-3. Dashborad.jsx
트러블 슈팅
문제 발생
나만의 포켓몬은 6개까지 저장이 가능하고 만약 포켓몬이 6개 모두 채워지지 않았을 경우에 나머지는 포켓볼 이미지로 대체하여야 했다. 그러나 나는 map 메서드를 이용해 선택된 포켓몬을 단순하게 추가하는 방식으로 구현을 하고 있었다.
원인 추론
6개의 빈 배열을 만들어야 겠다는 생각에 처음에는 단순하게 const arr = [0,0,0,0,0,0]을 만들어서 map 메서드로 돌릴 생각을 했으나 만약 포켓몬을 7개 혹은 8개 등 보유 갯수가 계속 바뀔 경우 좋지 않은 방법이라 다른 방법을 찾는 것이 좋을 것이라는 생각이 들었다. 쉽게 방식이 떠오르지 않았고 Array().fill().map() 체이닝을 이용해야 한다는 힌트를 얻었다.
해결 방안 : Array().fill().map() 체이닝
- Array(6)는 6개 짜리 빈 배열을 만들어 준다. 그리고 여기에 체이닝으로 Array(6).fill()을 하면 6개 짜리 배열안에 undefined가 채워진다.
| Array(6).fill() |
|---|
  |
- Array(6).fill()에 map 메서드 체이닝
| Array().fill().map() 체이닝을 이용한 예시 |
|---|
 |
결과

<StDashboard>
<h2> 나만의 포켓몬 </h2>
<StDashbordUl>
{Array(6)
.fill()
.map((_, i) => (
<li key={i}>
{selectedPokemon[i] ? (
<PokemonCard
pokemon={selectedPokemon[i]}
onDeleteHandler={onDeleteHandler}
isSelected={true}
/>
) : (
<StImg src={defaultImg}></StImg>
)}
</li>
))}
</StDashbordUl>
</StDashboard>
2-4. PokemonCard.jsx
<StPokemonCard>
<StPokemonName>
{" "}
<span> {String(pokemon.id).padStart(3, "0")} </span>
{pokemon.korean_name}
</StPokemonName>
<Link to={`/detail?id=${pokemon.id}`}>
<CardImg src={pokemon.img_url} alt={pokemon.korean_name} />
</Link>
{!isSelected ? (
<Button
color={pokemon.isSelected && "red"}
type="button"
onClick={() => onAddHandler(pokemon)}
>
{!pokemon.isSelected ? "추가" : "추가됨"}
</Button>
) : (
<Button
color="red"
type="button"
onClick={() => onDeleteHandler(pokemon.id)}
>
{" "}
삭제{" "}
</Button>
)}
</StPokemonCard>
3. 디테일 페이지 구현
 |  |
|---|
| No.001일 경우 이전 페이지로 이동을 마지막 포켓몬인 뮤가 뜨게 구현 | No.151일 경우 다음 페이지로 이동을 첫 번째 포켓몬인 이상해씨가 뜨게 구현 |
const Detail = ({ onAddHandler, newMockList }) => {
const navigate = useNavigate();
const [query] = useSearchParams();
const detailPokemonId = +query.get("id");
const pokemon = newMockList.find((p) => p.id === detailPokemonId);
const prePokemon = newMockList.find((p) =>
pokemon.id === 1
? p.id === newMockList.length
: p.id === detailPokemonId - 1
);
const nextPokemon = newMockList.find((p) =>
pokemon.id === newMockList.length
? p.id === 1
: p.id === detailPokemonId + 1
);
const prevDetail =
detailPokemonId === 1
? `/detail?id=${newMockList.length}`
: `/detail?id=${detailPokemonId - 1}`;
const nextDetail =
detailPokemonId === newMockList.length
? `/detail?id= 1`
: `/detail?id=${detailPokemonId + 1}`;
return (
<>
<DetailLinkSection>
<Link to={prevDetail}>
{" "}
◀︎ No.{String(prePokemon.id).padStart(3, "0")}{" "}
{prePokemon.korean_name}{" "}
</Link>
<Link to={nextDetail}>
{" "}
No.{String(nextPokemon.id).padStart(3, "0")} {nextPokemon.korean_name}
▶︎{" "}
</Link>
</DetailLinkSection>
<DetailSection>
<img src={pokemon.img_url} alt={pokemon.korean_name} />
<div>
<div className="pokemon-number">
No.{String(pokemon.id).padStart(3, "0")}
</div>
<div className="pokemon-name">{pokemon.korean_name}</div>
<div className="pokemon-description"> {pokemon.description}</div>
<div className="pokemon-type"> 타입 : {String(pokemon.types)} </div>
<div className="pokemon-btn-div">
<Button type="button" onClick={() => navigate("/dex")}>
돌아가기
</Button>
<Button
color={pokemon.isSelected && "yellow"}
type="button"
onClick={() => onAddHandler(pokemon)}
>
{!pokemon.isSelected ? "추가" : "보유"}
</Button>
</div>
</div>
</DetailSection>
{}
<SelectedPokemonSection>
{newMockList.map((pokemon) => {
if (pokemon.isSelected) {
return (
<div key={pokemon.id} className="selected-pokemon">
<img src={pokemon.img_url} />
<div className="pokemon-name"> {pokemon.korean_name} </div>
</div>
);
}
})}
</SelectedPokemonSection>
</>
);
};
(+나름 반응형으로 되게끔 구현했다.)