그동안은 파일을 세부적으로 나누지 않고 대부분 한 파일에 다 작업을 했었다. 그래서 코드 위치나 시점을 지금처럼 생각하지 않아도 됐었다.
컴포넌트를 총 6개로 작업하다보니 import해오는것도 신경써야하고 이 이벤트를 어느 컴포넌트에 작성해야할지 꽤나 고생시러웠다😢
크고 작은 에러 (김에러씨) 가 있었지만 그 중에서 카드교체 기능으로 수정하면서 생겼던 오류와 해결했던 과정을 정리해보았다.
위 이미지처럼 작동이 되어야하는데 나는 빈 Dashboard에 추가한 카드만 뜨는 방식이었다.
처음 빈 카드를 만들려면 useState의 초기값이 6개 만들어야했다.
어떻게 만들어야할지 고민하다가 null
을 사용해보기로 했다.
아무것도 선택하지 않은 상태의 카드 = null
로 지정
const INIT_TEXT = [null, null, null, null, null, null];
const [selectedPokemon, setSelectedPokemon] = useState(INIT_TEXT);
// 🍯 약간의 꿀팁
const INIT_TEXT = Array.from({ length: 6 }, () => null);
다소 무식?하게 null을 6번 적었는데 아래 주석처럼 Array.from
을 사용해서 가독성이나 유지보수하기 좋은 코드를 작성할 수 있다.
const addPokemon = (pokemon) => {
// 01. 중복체크
const isAlreadySelected = selectedPokemon.some((item) => {
return item && item.id === pokemon.id;
});
// 02. 최대 6개 체크
const selectedCount = selectedPokemon.filter(
(item) => item !== null
).length;
// 03. 선택된 포켓몬 리스트에 추가
const firstNullIndex = selectedPokemon.indexOf(null);
if (firstNullIndex !== -1) {
const newSelectedPokemon = [...selectedPokemon];
newSelectedPokemon[firstNullIndex] = pokemon;
setSelectedPokemon(newSelectedPokemon);
}
};
중복체크
const isAlreadySelected = selectedPokemon.some((item) => {
return item && item.id === pokemon.id;
});
// 초기값 null 주기 전 코드
if (selectedPokemon.length >= 6) {
alert("최대 6개까지만 선택할 수 있습니다.");
return;
처음에는 id
값만 일치하는지 분별했는데 초기값을 null
로 주면서 null
이 아니고, id
값도 일치하는가 2번의 검사가 필요하다.
최대 6개 체크
const selectedCount = selectedPokemon.filter(
(item) => item !== null
).length;
카드 개수가 6개인지 체크하는 부분도 사용자가 추가한 카드는 item이 null이 아닐 때니까 그 값을 가지고 filter
를 해주었다.
선택된 포켓몬 리스트에 추가
const firstNullIndex = selectedPokemon.indexOf(null);
if (firstNullIndex !== -1) {
const newSelectedPokemon = [...selectedPokemon];
newSelectedPokemon[firstNullIndex] = pokemon;
setSelectedPokemon(newSelectedPokemon);
}
추가버튼을 누른 카드가 대쉬보드 쪽으로 교체?되는 부분은 이렇게 작성했다.
먼저 indexOf
로 null
인지 확인하고 indexOf
출력이 -1 이면 없다는 뜻이니까 -1이 !==
아니라면 기존 selectedPokemon
state를 활용해서 추가하는 식으로 작성했다.
선택구현사항이었는데 매번 alert
만 사용하는게 질려서 새로 바꿔보았다.
https://www.npmjs.com/package/react-toastify
React-Toastify 를 사용했고 css 부분이라 간단하게 기록만 해두려한다.
// 01. toast 라이브러리 import 하기
import { ToastContainer, toast } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
// 02. 경고창 함수 만들기
const notify = (message) =>
toast(message, {
position: "top-center",
});
// 03. notify 함수 적용시키기
if (isAlreadySelected) {
notify("이미 선택한 포켓몬입니다.");
return;
}
if (selectedCount >= 6) {
notify("최대 6개까지 선택할 수 있습니다.");
return;
}
alert 라고 적었던 걸 notify 라고 적은거랑 똑같다.
message 인자를 받아와서 해당 조건에 맞는 메시지가 뜨도록 했다.
순탄하게 넘어가면 내가 아니지... 이렇게 코드를 작성하면서 또 문제가 발생했는데 뭐였냐면 "이미 추가한 카드에서 경고 피드백이 뜨지 않는" 오류가 생겼다.
문제의 코드
const addPokemon = (pokemon) => {
const isAlreadySelected = selectedPokemon.some((item) => {
return item && item.id === pokemon.id;
});
if (isAlreadySelected) {
notify("이미 선택한 포켓몬입니다.");
return;
}
};
생각해 본 문제점
맨 처음과 추가된 카드 있을 때의 state 변화 확인하기
null
로 주니까 기존 로직으로 하면 null
값을 못 읽어왔음 ⇒ item이 null 인지 판단해서 해결함state에는 문제가 없으니까 추가버튼 이벤트 함수를 콘솔로 찍어봤다.
이미 추가된 카드를 또 누르면 아예 클릭이벤트가 작동하지 않았다.
반복되어 콘솔이 찍힌다거나 에러화면 조차 뜨지 않았음...
또 이미 선택한 카드라면 false
에서 true
로 바뀌어야하는데 버튼이 동작하지 않으면서 이것도 보이지 않았다.
🔍 수정한 코드
// PokeList.jsx
return (
<PokemonCard
key={pokemon.id}
pokemon={pokemon}
onAdd={() => {
if (!isSelected) {
//!! 이미 Context 파일에서 검사해줬는데 또 해버림
addPokemon(pokemon);
}
}}
isSelected={isSelected}
/>
);
문제의 코드는 Context
파일이 아니라 PokeList
컴포넌트에 있었다.
이미 Context 작업을 하면서 addPokemon
로직을 짤 때 조건까지 다 했는데 나는 여기서 또 조건을 걸어버린 것이다.
return (
<PokemonCard
key={pokemon.id}
pokemon={pokemon}
onAdd={() => {
addPokemon(pokemon);
}}
isSelected={isSelected}
/>
);
이렇게 바꿔주고 다시 콘솔을 확인해보면~
원하던대로 콘솔에도 나오고 이미 클릭한 카드는 true
가 나오는 걸 확인할 수 있었다!
따흑흑 따흑흑 이 화면을 얼마나 보고싶었던지 흑흑흑ㅠㅠㅠ
저번 포스팅에서도 썼지만 정말 시점도 잘 봐야하고 중복된 코드가 없는지도 꼭 확인해야겠다. 항상 빨간색 에러창만 보다가 콘솔에도 안 뜨는 경우를 만나니까 이것대로 고통이었다...
완성된 코드로 오늘 TIL도 끝내야지!
PokemonContext 컴포넌트
const addPokemon = (pokemon) => {
// 01. 중복체크
const isAlreadySelected = selectedPokemon.some((item) => {
return item && item.id === pokemon.id;
});
if (isAlreadySelected) {
notify("이미 선택한 포켓몬입니다.");
return;
}
// 02. 최대 6개 체크
const selectedCount = selectedPokemon.filter(
(item) => item !== null
).length;
if (selectedCount >= 6) {
notify("최대 6개까지 선택할 수 있습니다.");
return;
}
// 03. 선택된 포켓몬 리스트에 추가
const firstNullIndex = selectedPokemon.indexOf(null);
if (firstNullIndex !== -1) {
const newSelectedPokemon = [...selectedPokemon];
newSelectedPokemon[firstNullIndex] = pokemon;
setSelectedPokemon(newSelectedPokemon);
}
};
PokeList 컴포넌트
import styled from "styled-components";
import PokemonCard from "../components/PokemonCard";
import { useContext } from "react";
import { PokemonContext } from "/src/context/PokemonContext.jsx";
const PokemonList = ({ pokemonList }) => {
const { addPokemon, selectedPokemon } = useContext(PokemonContext);
return (
<ListContainer>
{pokemonList.map((pokemon) => {
const isSelected = selectedPokemon.some(
(selected) => selected && selected.id === pokemon.id
);
return (
<PokemonCard
key={pokemon.id}
pokemon={pokemon}
onAdd={() => {
addPokemon(pokemon);
}}
isSelected={isSelected}
/>
);
})}
</ListContainer>
);
};
export default PokemonList;
에러왕 김에러