Q6. 카드 브랜드 식별 로직 — 폼 값일까, 도메인 모델일까?
크루의 질문
“브랜드 로직은 어디에서 알고 있는 게 맞는가.” — 포도·레스 페어
“cardBrand 값을 formValue로써 관리해야 할지 별도 변수로 관리해야 할지.” — 아지·애니 페어
AS-IS 코드
🔍 읽기 전에
cardBrand를 알려면 어떤 정보가 필요한가요?
그 정보가 cardNumber에 이미 있다면, cardBrand는 상태일까요, 파생값일까요?
// src/components/AddCardPage/AddCardForm/index.tsx
const [cardBrand, setCardBrand] = useState<CardBrand>({ name: '', color: '' });
const [cardNumber, setCardNumber] = useState<CardNumberState>(['', '', '', '']);
const [expDate, setExpDate] = useState<ExpDate>({ year: '', month: '' });
const [password, setPassword] = useState<PasswordState>(['', '']);
const [ownerName, setOwnerName] = useState<string>('');
const [nickname, setNickname] = useState<string>('');
const [CVC, setCVC] = useState<string>('');
const [isCardBrandModalVisible, setIsCardBrandModalVisible] = useState<boolean>(false);
const [isNicknameModalVisible, setIsNicknameModalVisible] = useState<boolean>(false);
const onSelectCardBrand = (cardBrand: CardBrand) => {
setCardBrand(cardBrand);
setIsCardBrandModalVisible(false);
};
const onSetCardBrand = () => {
if (cardNumber[0].length !== CARD_NUMBER_DIGITS || cardNumber[1].length !== CARD_NUMBER_DIGITS) {
setCardBrand({ name: '', color: '' });
return;
}
const cardBrand = CARD_BRAND[Number(cardNumber[1][3])];
if (!cardBrand) {공식문서 단서 + 도메인 노트
React 공식문서 Avoid redundant state (한글 번역 — 5원칙 중 3번):
If you can calculate some information from the component’s props or its existing state variables during rendering, you should not put that information into that component’s state.
cardBrand가 cardNumber로부터 결정 가능하다면 별도 상태로 두는 것은 redundant. 그러나 2단계 요구사항(“사용자는 카드사를 선택할 수 있고, 선택에 따라 프리뷰 카드의 색상이 변경된다”)은 사용자 선택도 입력원이 된다 — 즉 brand는 (자동 식별 ∪ 사용자 선택) 두 입력의 해소다.
도메인 노트: 카드 브랜드 식별 규칙은 React가 모르는 영역이다. 페이먼츠 2단계의 카드사 식별 번호 규칙(Diners 36/14자리, AMEX 34·37/15자리, UnionPay 622126부터 622925까지 등)은 순수 함수로 분리할 수 있다. 컴포넌트 외부의 도메인 모듈에 두면 React 16/17/18/19가 어떻게 바뀌든 영향받지 않는다. 이게 1단계에서 지금 도메인 분리를 시작할 가치다.
선배 PR 읽기 가이드
선배 PR 읽기 가이드 — 펼쳐보기
선배 PR — 폼 상태에 cardBrand를 같이 둔 패턴
읽는 관점: “cardBrand를 입력 form state에 저장하는지, 카드번호에서 계산하는지 비교하세요. 리뷰에서 파생값과 원천 입력값을 구분하는 표현을 찾는 것이 핵심입니다.”
선배 PR — cardNumber로부터 매번 계산하는 패턴
읽는 관점: “카드번호 앞자리나 특정 index로 브랜드를 계산하는 코드를 보세요. 2단계의 카드사 선택 UX가 들어오면 식별값과 사용자 선택값이 어떻게 달라지는지 연결해볼 수 있습니다.”
선배 PR — 도메인 모듈로 분리한 패턴
읽는 관점: “cardCompany, cardBrand, bank 상수/유틸 파일을 보세요. 컴포넌트 밖 도메인 모듈로 빠졌을 때 테스트와 변경 범위가 어떻게 줄어드는지가 읽는 포인트입니다.”
추가 읽을거리 — Kent C. Dodds
외부 글 모음 — 펼쳐보기
Don’t Sync State. Derive It!
- 원문: kentcdodds.com/blog/dont-sync-state-derive-it
- 한 줄 요약:
cardBrand를cardNumber로부터 파생시키면 두 상태를 동기화할 필요가 없다. 그러나 사용자 선택이 들어오면 — 두 입력원을 어떻게 다룰지가 새로운 학습 포인트가 된다. 이 글이 그 시작점.
연관 PR 더 보기
이 주제에 매핑된 1단계 PR 전체 — 펼쳐보기
자기 가설에 가까운 PR을 골라 추가로 비교해보세요.
| PR | 작성자 | 핵심 키워드 |
|---|---|---|
| #2 | @bigsaigon333 | 카드사/브랜드 판별 |
| #3 | @jho2301 (대표) | 객체/배열 state 구조 / 카드번호 4분할 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #4 | @SunYoungKwon (대표) | 객체/배열 state 구조 / 카드번호 4분할 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #5 | @zereight | 카드사/브랜드 판별 |
| #6 | @Puterism | state 위치 / 공통 Input/컴포넌트 합성 / 카드사/브랜드 판별 |
| #9 | @0307kwon | state 위치 / 객체/배열 state 구조 / 카드번호 4분할 / 카드사/브랜드 판별 |
| #11 | @Tanney-102 | state 위치 / 객체/배열 state 구조 / 카드번호 4분할 / 카드사/브랜드 판별 |
| #12 | @seojihwan | 카드사/브랜드 판별 |
| #13 | @yujo11 | 카드사/브랜드 판별 |
| #14 | @sunhpark42 | 카드사/브랜드 판별 |
| #15 | @365kim | 객체/배열 state 구조 / 공통 Input/컴포넌트 합성 / 카드사/브랜드 판별 |
| #16 | @shinsehantan | 객체/배열 state 구조 / 공통 Input/컴포넌트 합성 / 카드사/브랜드 판별 |
| #17 | @zigsong | 객체/배열 state 구조 / 카드번호 4분할 / 공통 Input/컴포넌트 합성 / 카드사/브랜드 판별 |
| #18 | @0imbean0 | 카드번호 4분할 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #19 | @iborymagic | 공통 Input/컴포넌트 합성 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #25 | @2SOOY | 공통 Input/컴포넌트 합성 / 카드사/브랜드 판별 |
| #26 | @igy95 | 객체/배열 state 구조 / 카드사/브랜드 판별 |
| #28 | @jum0 | 공통 Input/컴포넌트 합성 / 카드사/브랜드 판별 |
| #29 | @swon3210 | state 위치 / 카드번호 4분할 / 공통 Input/컴포넌트 합성 / 카드사/브랜드 판별 |
| #67 | @devhyun637 | 카드사/브랜드 판별 |
| #79 | @wonsss | 검증·에러 처리 / 카드사/브랜드 판별 |
| #81 | @usageness | 공통 Input/컴포넌트 합성 / 카드사/브랜드 판별 |
| #82 | @hwangstar156 | state 위치 / 카드번호 4분할 / 카드사/브랜드 판별 |
| #85 | @lokba | state 위치 / 객체/배열 state 구조 / 카드사/브랜드 판별 |
| #93 | @kwannee | 객체/배열 state 구조 / 공통 Input/컴포넌트 합성 / 카드사/브랜드 판별 |
| #94 | @euijinkk | state 위치 / 객체/배열 state 구조 / 카드번호 4분할 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #98 | @byhhh2 | state 위치 / 객체/배열 state 구조 / 공통 Input/컴포넌트 합성 / 카드사/브랜드 판별 |
| #101 | @sanaandmomo | state 위치 / 카드사/브랜드 판별 |
| #105 | @ronci | custom hook / 카드번호 4분할 / 카드사/브랜드 판별 |
| #186 | @bassyu | 공통 Input/컴포넌트 합성 / 카드사/브랜드 판별 |
| #190 | @dladncks1217 | state 위치 / 객체/배열 state 구조 / 카드번호 4분할 / 카드사/브랜드 판별 |
| #202 | @kangyeongmin | 카드사/브랜드 판별 |
| #218 | @tkdrb12 | 객체/배열 state 구조 / 카드번호 4분할 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #331 | @anttiey | state 위치 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #332 | @seongjinme | 카드번호 4분할 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #333 | @jinyoung234 | 객체/배열 state 구조 / 카드번호 4분할 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #334 | @vi-wolhwa | state 위치 / 객체/배열 state 구조 / 카드번호 4분할 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #335 | @healim01 | 객체/배열 state 구조 / 공통 Input/컴포넌트 합성 / 카드사/브랜드 판별 |
| #336 | @jaeml06 | 객체/배열 state 구조 / 카드번호 4분할 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #337 | @jinhokim98 | custom hook / 객체/배열 state 구조 / 카드번호 4분할 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #338 | @Largopie | custom hook / 객체/배열 state 구조 / 카드번호 4분할 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #340 | @chosim-dvlpr | 객체/배열 state 구조 / 카드번호 4분할 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #341 | @simorimi | state 위치 / 객체/배열 state 구조 / 카드번호 4분할 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #343 | @brgndyy | custom hook / 객체/배열 state 구조 / 카드번호 4분할 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #344 | @soi-ha | state 위치 / 객체/배열 state 구조 / 카드번호 4분할 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #345 | @lurgi | 객체/배열 state 구조 / 카드번호 4분할 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #346 | @chlwlstlf | state 위치 / 카드사/브랜드 판별 |
| #347 | @useon | 객체/배열 state 구조 / 카드사/브랜드 판별 |
| #348 | @pp449 | state 위치 / 객체/배열 state 구조 / 카드번호 4분할 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #350 | @pakxe | 검증·에러 처리 / 카드사/브랜드 판별 |
| #351 | @novice0840 | 객체/배열 state 구조 / 카드번호 4분할 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #354 | @hwinkr | state 위치 / 객체/배열 state 구조 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #355 | @greetings1012 | 객체/배열 state 구조 / 카드번호 4분할 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #357 | @00kang | state 위치 / 객체/배열 state 구조 / 카드번호 4분할 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #358 | @dle234 | state 위치 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #359 | @rbgksqkr | custom hook / 카드번호 4분할 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #360 | @llqqssttyy | 객체/배열 state 구조 / 카드번호 4분할 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #361 | @skiende74 | custom hook / 객체/배열 state 구조 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #362 | @Jaymyong66 | 객체/배열 state 구조 / 카드번호 4분할 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #363 | @chysis | state 위치 / 공통 Input/컴포넌트 합성 / 카드사/브랜드 판별 |
| #365 | @ImxYJL | 객체/배열 state 구조 / 카드번호 4분할 / 카드사/브랜드 판별 |
| #366 | @Parkhanyoung | custom hook / 카드번호 4분할 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #367 | @ss0526100 | custom hook / 객체/배열 state 구조 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #368 | @Todari | 객체/배열 state 구조 / 카드번호 4분할 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #409 | @keemsebin | state 위치 / 객체/배열 state 구조 / 카드사/브랜드 판별 |
| #411 | @AHHYUNJU | state 위치 / 카드번호 4분할 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #412 | @mlnwns | state 위치 / 객체/배열 state 구조 / 카드번호 4분할 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #417 | @sooyeoniya | custom hook / 카드번호 4분할 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #418 | @MinSungJe | state 위치 / 객체/배열 state 구조 / 카드번호 4분할 / 카드사/브랜드 판별 |
| #419 | @spoyodevelop | 객체/배열 state 구조 / 카드번호 4분할 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #420 | @ShinjungOh | state 위치 / 카드번호 4분할 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #421 | @ExceptAnyone | state 위치 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #422 | @thgml05 | 객체/배열 state 구조 / 카드번호 4분할 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #423 | @yeji0214 | state 위치 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #424 | @guesung | 카드번호 4분할 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #425 | @Db0111 | state 위치 / 객체/배열 state 구조 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #427 | @sanghee01 | 객체/배열 state 구조 / 카드번호 4분할 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #429 | @youdame | 카드사/브랜드 판별 |
| #430 | @yeongipark | state 위치 / 객체/배열 state 구조 / 카드번호 4분할 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #432 | @ha-kuku | 카드번호 4분할 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #433 | @eunsoA | state 위치 / 객체/배열 state 구조 / 카드번호 4분할 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #434 | @minji2219 | state 위치 / 카드번호 4분할 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #435 | @dev-dino22 | state 위치 / 객체/배열 state 구조 / 카드번호 4분할 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #436 | @bunju20 | 카드번호 4분할 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #437 | @aydenote | 객체/배열 state 구조 / 카드번호 4분할 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #439 | @kimyou1102 | custom hook / 객체/배열 state 구조 / 카드번호 4분할 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #441 | @hanheel | 카드번호 4분할 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #442 | @jaeyoung-kwon | state 위치 / 공통 Input/컴포넌트 합성 / 카드사/브랜드 판별 |
| #443 | @jin123457 | 카드번호 4분할 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #444 | @mun-kyeong (대표) | state 위치 / 객체/배열 state 구조 / 카드번호 4분할 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #445 | @Daeun-100 | state 위치 / 카드번호 4분할 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #446 | @JeLee-river | state 위치 / 객체/배열 state 구조 / 공통 Input/컴포넌트 합성 / 카드사/브랜드 판별 |
| #447 | @shuyeon | state 위치 / 객체/배열 state 구조 / 카드사/브랜드 판별 |
| #448 | @hoyyChoi | state 위치 / 객체/배열 state 구조 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #453 | @ExceptAnyone | state 위치 / 검증·에러 처리 / 카드사/브랜드 판별 |
195개 전체 인덱스(다른 주제 포함)는 부록에서 확인할 수 있습니다.





