ENUM에서 value가 같으면 동일한 의미일 것이라는 가정으로 키워드 매핑 오류가 발생했습니다. 렌더링 단계에서는 value만 보고 매칭하기 때문에 구분이 불가능했고, 이 때문에 UI에 잘못된 정보가 표시되는 UX 문제가 발생했습니다. 이를 해결하기 위해
카테고리 + key 조합을 고유 식별자로 사용하는 방식을 enum 구조에 적용해 문제를 해결했습니다.
이번 리팩터링으로 단순히 오류를 해결하는 것을 넘어서, Personality와 PreferredPeople처럼 동일한 key 구조를 가진 다른 enum들에도 확장 가능하도록 전체적인 구조를 정리하는 계기가 되었습니다. 작은 텍스트 하나가 사용자에게는 직접적인 정보이기 때문에 키워드가 어떤 의미를 갖고 어떤 맥락인지 확실히 구분해주는 것이 얼마나 중요한지 다시 한 번 실감했습니다.
+) 실제로 개발하며 각각 다른 방식으로 선언되어있는 ENUM값을 관리하기 번거롭다는 생각을 하게 되었고, 더 쉽게 관리할 수 있도록 prefix 규칙을 자동으로 정의해줘 고유한 ENUM 키 값을 생성하고 관리해주는 TypeScript 유틸리티 라이브러리를 개발하게 되었습니다!
개발하고 있는 튜닝 서비스는 조직 내 유저를 관심사에 따라 매칭해주는 서비스인데, 프로젝트의 개인정보 입력 단계에서 '나의 성향'과 '상대방의 성향'을 같은 ENUM값으로 사용하고 있었고, 이로 인해 한 카테고리에 ENUM 값이 전부 중복되어 존재하고 있었습니다.

언뜻 보기에는 문제없어 보였지만, 실제 UI 렌더링 과정에서 이 값이 충돌하며 잘못된 키워드가 표시되는 문제가 발생했습니다. 이번 글에서는 어떤 상황에서 문제가 발생했고, 원인이 무엇이었는지 그리고 이를 어떻게 해결했는지 상세하게 기록해보려고 합니다.
어떤 문제가 발생했나요?
문제의 핵심은 다음과 같습니다.
- 흡연(SMOKING)의 금연중 → "TRYING_TO_QUIT"
- 음주(DRINKING)의 금주중 → "TRYING_TO_QUIT"
두 항목은 서로 다른 의미를 갖지만 동일한 enum value를 사용하고 있었습니다.
이 때문에 매핑 과정에서 충돌이 발생했고, 다음과 같은 문제가 실제로 나타났습니다:
- "smoking": "TRYING_TO_QUIT"을 선택했는데 UI에는 금주중이 표시됨
- 반대로 "drinking": "TRYING_TO_QUIT"이 들어와도 금연중이 표시될 위험 존재
- 결과적으로 동일한 value를 사용하는 키워드 중 하나만 정상적으로 렌더링되는 현상 발생
문제는 키워드 태그를 렌더링할 때 value만으로 label을 매칭하는 구조에서 나타났습니다.
예상 결과
각 카테고리가 다르기 때문에 value가 같더라도 아래처럼 정확하게 구분되어야 합니다.
- "smoking": "TRYING_TO_QUIT" → 금연중
- "drinking": "TRYING_TO_QUIT" → 금주중
즉 카테고리를 기준으로 정확히 연결되는 매핑 구조가 필요했습니다.
원인 분석
버그의 핵심적인 원인은 value만 가지고 키워드를 매칭한 것이었습니다.
// 잘못된 예
const label = Object.values(ALL_KEYWORD_MAP).find(v => v === key);
// 키가 아닌 값만 보고 찾음 → 중복 시 문제
- "TRYING_TO_QUIT"이 흡연/음주 양쪽에 존재하면
- 어떤 카테고리에서 온 데이터인지 알 수 없음
- 결국 마지막으로 매핑된 항목이 덮어쓰게 됨
prefixEnum(category, enumObj)를 사용하지 않고 key 없이 value 기준으로만 비교하거나, value만을 사용해 렌더링하는 경우에 문제가 발생했습니다. value만 보고 매핑하는 경우, 어떤 카테고리에서 온 키워드인지 구분이 안 되기 때문에 결과적으로 마지막에 덮어쓴 항목이 표시되는 근본적인 문제가 있었습니다.
해결 방법
1. prefixEnum 도입하기
가장 각 enum 값에 카테고리명을 prefix로 붙여서 고유한 key를 만들었습니다. 기존에는 흡연(SMOKING)과 음주(DRINKING)에서 모두 "TRYING_TO_QUIT"이라는 동일한 value를 사용하고 있었기 때문에 단순히 value만 보고는 이게 금연중인지, 금주중인지를 구분할 수 없었습니다. 그래서 아래와 같이 prefixEnum 유틸 함수를 도입했습니다.
prefixEnum('SMOKING', Smoking);
// → { SMOKING_TRYING_TO_QUIT: '금연중', ... } prefixEnum('DRINKING', Drinking);
// → { DRINKING_TRYING_TO_QUIT: '금주중', ... }
이렇게 하면 모든 키워드가 SMOKING_TRYING_TO_QUIT, DRINKING_TRYING_TO_QUIT처럼 `카테고리 + 원래 key` 조합으로 변환되기 때문에, 값이 "TRYING_TO_QUIT"로 같더라도 서로 충돌하지 않고 안전하게 매핑할 수 있습니다.
정리하자면
- 기존: "TRYING_TO_QUIT"이라는 value만 존재했기 때문에 카테고리 정보가 유실되었지만,
- 이후: "SMOKING_TRYING_TO_QUIT", "DRINKING_TRYING_TO_QUIT"처럼 고유 key로 분리해 카테고리와 의미를 함께 보존할 수 있었습니다.
이 단계에서 enum 설계 자체가 더 명확해졌고, 다른 카테고리 간 키 충돌 가능성도 함께 줄일 수 있었습니다.
2. enum 값 사용 시 ${CATEGORY}_${KEY} 형태로 일관성 유지하기
백엔드에서 내려오는 원본 데이터는 예를 들어 다음과 같은 형태였습니다.
{ "smoking": "TRYING_TO_QUIT", "drinking": "TRYING_TO_QUIT" }
이 상태로는 여전히 "TRYING_TO_QUIT"만 보고는 어떤 카테고리에서 온 값인지 알 수 없기 때문에, 프론트에서 렌더링하기 전에 먼저 카테고리 정보를 붙여주는 전처리 과정을 넣었습니다.
const keywordList = Object.entries(keywords).map( ([category, value]) => `${category.toUpperCase()}_${value}` );
// ["SMOKING_TRYING_TO_QUIT", "DRINKING_TRYING_TO_QUIT"]
이렇게 하면 이후 로직에서는 더 이상 smoking / drinking / TRYING_TO_QUIT를 각각 따로 관리할 필요 없이, 항상 prefix된 하나의 문자열 키(SMOKING_TRYING_TO_QUIT)로만 다루면 되기 때문에 코드의 일관성이 좋아집니다.
렌더링할 때도, 태그를 필터링할 때도, 어디에서든 동일한 규칙(${CATEGORY}_${KEY})에 따라 접근할 수 있어서 enum을 사용하는 모든 흐름이 단순해지고, 이 값이 어느 카테고리에서 온 것인지를 보존한 채로 처리할 수 있습니다.
3. 매핑도 prefix 기준으로 정확히 조회
UI에 실제로 보여줄 한글 라벨을 찾는 과정에서도 value 기반 탐색을 완전히 제거하고, 앞에서 만든 prefix 기반 키를 그대로 사용하는 방식으로 변경했습니다.
const label = ALL_KEYWORD_MAP[`${category.toUpperCase()}_${value}`];
기존에는 다음과 같이 Object.values()에서 value만 보고 찾는 코드가 있었습니다.
// ❌ 문제를 일으켰던 방식
const label = Object.values(ALL_KEYWORD_MAP).find(v => v === key);
이 방식은 `금연중` / `금주중`처럼 한글 value가 겹치거나, "TRYING_TO_QUIT"처럼 enum value가 겹치는 경우 어느 카테고리에 속한 값인지 전혀 알 수 없어서, 마지막에 덮어쓴 항목이 선택되는 구조였습니다.
반면 지금 구조에서는 전처리 단계에서 ${category.toUpperCase()}_${value} 형태의 key를 만들고 ALL_KEYWORD_MAP 역시 같은 규칙으로 구성해둔 뒤 렌더링 시에도 동일한 key로 바로 맵핑 하는 방식이라 중간에 헷갈릴 여지가 없습니다.
흡연이든 음주든, Personality든 PreferredPeople이든 prefix가 다르면 완전히 다른 키로 취급되기 때문에 중복 enum value로 인한 충돌 없이 항상 의도한 한글 라벨을 정확하게 가져올 수 있습니다.
회고
이번 문제는 단순히 ENUM의 value가 같다는 이유로 하나로 취급되어 잘못된 결과가 나온 케이스였습니다. 이를 통해 느낀 점은
- value는 같을 수 있어도 의미와 context는 다르다. "TRYING_TO_QUIT"은 금연중일 수도, 금주중일 수도 있다. 이 차이를 컴포넌트 단에서 정확하게 인식하고 분리하는 게 중요합니다.
- enum을 렌더링하는 구조에서는 단순 value 기반 비교보다, category + key 조합으로 고유 식별자를 만드는 방식이 안정적입니다.
- 이번에 prefix 구조를 도입한 것은 단순한 보완이 아니라 전체 enum 설계를 명확하게 분리해주는 계기가 되었고, 키워드 중 내 성향인 Personality / 선호하는 사람의 성향인 PreferredPeople의 enum 키 - 값이 완전히 똑같았는데, 이 충돌 또한 같이 방지할 수 있는 기반이 되었습니다.
- UI에 표현되는 텍스트가 사용자에게 직접 전달되는 만큼, 작은 혼동도 명확하게 구분해서 대응해야 한다는 교훈을 얻었습니다.
지금은 단일 키워드 기준이지만, 추후 멀티셀렉트 또는 더 많은 키워드가 사용될 경우를 대비해 구조적 유연성도 함께 고려해야겠다는 생각이 들었습니다.
튜닝 프론트엔드 레포지토리 → https://github.com/100-hours-a-week/2-hertz-fe
GitHub - 100-hours-a-week/2-hertz-fe: ⚡️ 2조 튜닝 FE 레포지토리
⚡️ 2조 튜닝 FE 레포지토리. Contribute to 100-hours-a-week/2-hertz-fe development by creating an account on GitHub.
github.com
'👩🏻💻 Develop > TroubleShooting' 카테고리의 다른 글
| [Next.js] ENUM 키워드 매핑 실패로 인해 발생한 한글 변환 오류 해결하기 (0) | 2025.11.25 |
|---|---|
| [Next.js] Refresh Token 만료로 인한 페이지 무한 새로고침 오류 해결하기 (0) | 2025.09.07 |
| [Next.js] useSearchParams 사용 시 Next.js 15 빌드 실패 이슈 (0) | 2025.09.06 |
| [Next.js] next-pwa와 Turbopack 호환성 문제 해결하기 (0) | 2025.09.05 |
| [Next.js] SSE 브라우저 연결 오류 해결하기 (Nginx) (1) | 2025.09.02 |