React에서 관심사(취미) 선택 모달 구현하기

2025. 2. 16. 21:50프론트엔드/REACT

728x90

 


📌 React에서 관심사(취미) 선택 모달 구현하기

💡 개요

React에서 사용자가 관심사(취미)를 선택할 수 있는 모달(InterestSelectModal)을 구현하는 과정에서 발생할 수 있는 문제와 해결 방법을 정리한 포스트입니다.
이 글에서는 Recoil 상태 관리, props 전달, 배열 데이터 업데이트, UI 반영 방식 등을 다룹니다.


🔹 1. 관심사 선택 모달의 핵심 기능

사용자가 관심사를 선택하면 배열 형태로 저장되며, 선택한 관심사는 다시 모달에서 표시됩니다.
이 과정에서 고려해야 할 사항:

  1. interests 데이터를 배열 형태로 유지해야 함 → ['헬스', '맛집 탐방'] 같은 형식
  2. 클릭하면 관심사가 추가/제거될 수 있어야 함
  3. 최대 10개까지만 선택 가능해야 함
  4. 선택된 항목은 UI에서 스타일 변경(배경색 등) 되어야 함
  5. 모달이 닫힐 때 데이터가 유지되어야 함

이 기능을 구현하기 위해 React의 상태 관리와 이벤트 핸들링을 활용합니다.


🔹 2. 관심사 선택 모달 구현 (InterestSelectModal.jsx)

📌 1) interests가 배열 형태인지 확인

사용자가 선택한 관심사를 저장하는 interests는 배열 형태여야 합니다.
예를 들어, 아래처럼 ['헬스', '맛집 탐방'] 형식으로 저장되어야 합니다.

const [interests, setInterests] = useState(["헬스", "맛집 탐방"]);

⚡ useState가 아닌 Recoil을 사용할 경우 useRecoilState(profileEditState)로 관리할 수 있습니다.


📌 2) 관심사 목록 필터링 (한글 검색 지원)

관심사 목록을 검색할 때 한글 초성 검색 기능을 적용하여, 사용자가 "ㅎㅅ"을 입력하면 "헬스"가 검색되도록 설정합니다.

const filteredHobbyData = hobbyData.map((category) => ({
  ...category,
  hobbies: category.hobbies.filter((hobby) => {
    const decomposedHobby = decomposeHangul(hobby.name);
    const decomposedSearch = decomposeHangul(searchQuery);
    
    return decomposedSearch.every((searchChar, index) => {
      const hobbyChar = decomposedHobby[index];
      if (!hobbyChar) return false;

      if (decomposedSearch.length === 1) {
        return hobbyChar.chosung === searchChar.chosung;
      } else if (decomposedSearch.length === 2) {
        return (
          hobbyChar.chosung === searchChar.chosung &&
          hobbyChar.jungsung === searchChar.jungsung
        );
      } else {
        return (
          hobbyChar.chosung === searchChar.chosung &&
          hobbyChar.jungsung === searchChar.jungsung &&
          hobbyChar.jongsung === searchChar.jongsung
        );
      }
    });
  }),
}));

✅ decomposeHangul 함수를 사용해 초성 검색이 가능하도록 구현


📌 3) 클릭하면 관심사 추가/제거 (최대 10개 제한)

사용자가 관심사를 클릭하면 interests 배열을 업데이트해야 합니다.

  • 이미 선택된 경우 → 배열에서 제거
  • 선택되지 않은 경우 → 배열에 추가 (최대 10개 제한)
const handleHobbyClick = (name) => {
  const isAlreadySelected = interests.includes(name);
  const updatedHobbies = isAlreadySelected
    ? interests.filter((hobby) => hobby !== name) // 이미 선택된 경우 제거
    : interests.length < 10
    ? [...interests, name] // 최대 10개까지만 추가
    : interests;

  setInterests(updatedHobbies); // 새로운 배열로 업데이트
};

✅ setInterests를 사용해 배열을 직접 수정
✅ filter()를 사용해 이미 선택된 경우 삭제
✅ length < 10으로 최대 개수 제한


📌 4) 선택된 관심사 스타일 변경

UI에서 선택된 관심사는 배경색을 변경하여 사용자에게 피드백을 제공해야 합니다.

{category.hobbies.map((hobby, idx) => (
  <div
    key={idx}
    className={`hobby-items ${interests.includes(hobby.name) ? "selected" : ""}`}
    onClick={() => handleHobbyClick(hobby.name)}
  >
    <span className="hobby-emoji">
      {hobby.emoji} {hobby.name}
    </span>
  </div>
))}

✅ interests.includes(hobby.name)이 true이면 "selected" 클래스를 추가

CSS 코드 (InterestSelectModal.css)

.hobby-items {
  padding: 8px 12px;
  border-radius: 10px;
  cursor: pointer;
  transition: 0.2s;
}

.hobby-items.selected {
  background-color: #007aff;
  color: white;
}

✅ 선택된 관심사는 파란색 배경 & 흰색 글씨로 변경


🔹 3. 관심사 선택 데이터를 ProfileEdit 페이지에 반영

사용자가 관심사를 선택하면 profile.interests에 반영되어야 합니다.
ProfileEdit 페이지에서 InterestSelectModal을 다음과 같이 사용합니다.

<InterestSelectModal 
  isOpen={isInterestModalOpen} 
  onClose={() => setIsInterestModalOpen(false)}
  interests={profile.interests} // ✅ 배열로 전달
  setInterests={(newInterests) => setProfile({ ...profile, interests: newInterests })} // ✅ 배열 유지
/>

✅ interests가 배열 형태로 유지됨
✅ setProfile()을 사용하여 profile.interests를 업데이트


🔹 4. 최종적으로 완성된 관심사 선택 기능

🎯 사용자가 관심사를 선택하면 UI에 반영되며, 모달을 닫았다가 다시 열어도 유지됩니다.
🎯 10개 이상 선택할 수 없도록 제한되며, 클릭하면 추가/삭제가 가능합니다.
🎯 한글 초성 검색이 지원되어 편리한 검색이 가능합니다.


 

 

728x90