Q1. state(제어)를 어느 컴포넌트에서 관리해야 할까?
크루의 질문
“state 관리(제어)를 어느 컴포넌트에서 해야 할지 오래 고민했습니다. 컴포넌트 쪼개는 기준에 대해 얘기를 많이 했습니다.” — 파라디·피트·이현 페어
“각 입력 필드를 합성하는 최상위 컴포넌트에서 가지고 있을지, custom hook 안의 useState로 가지고 반환값을 사용해서 컴포넌트를 구성할지 고민중입니다.” — 콘티·먼지 페어
“3개 이상의 컴포넌트를 통과하는 상태 props는 전역 상태로 관리하는 것이 좋은가에 대해 논의해보았습니다.” — 지오·코브 페어
AS-IS 코드
🔍 읽기 전에
이 코드는 상태를 어디까지 끌어올렸나요?
하위 컴포넌트가 상태를 그대로 보유했다면 App은 무엇을 모를 수 있었을까요?
// src/App.tsx (PR #439 데이지)
import useCardCVC from './hooks/useCardCVC';
const {
cardNumber,
onChange,
checkCardNumberError,
isError,
errorMessage: cardNumberErrorMessage,
} = useCardNumber();
const {
cardValidityPeriod,
isErrorCardValidityPeriod,
onChangeCardValidityPeriod,
checkCardValidityPeriodError,
errorMessage: cardValidityPeriodErrorMessage,
} = useCardValidityPeriod();
const {
cardCVC,
isCardCVCError,
onChangeCVC,
checkCardCVCError,
errorMessage: cardCVCErrorMessage,
} = useCardCVC();App이 카드번호·만료일·CVC 세 종류 상태를 모두 알고 있다. 자식 컴포넌트들은 props로 받아 쓴다. 왜 여기까지 끌어올렸을까와 그 비용은 무엇이었을까가 이 카드의 학습 포인트다.
공식문서 단서 — State 끌어올리기
Sharing State Between Components 첫 단락 (한글 번역):
Sometimes, you want the state of two components to always change together. To do it, remove state from both of them, move it to their closest common parent, and then pass it down to them via props. This is known as lifting state up, and it’s one of the most common things you will do writing React code.
핵심 한 줄: 상태를 자식이 공유해야 할 때, 가장 가까운 공통 부모로 옮긴다. 페이먼츠에서는 프리뷰 카드가 카드번호·만료일·소유자명·브랜드를 동시에 보여주므로(CVC는 실제 카드처럼 프리뷰에 노출되지 않습니다), 입력 컴포넌트들과 프리뷰의 공통 부모가 어디인지가 결정의 기준이 된다. 반대로 CVC는 프리뷰가 모르는 값 — 이런 입력은 굳이 App까지 끌어올리지 않아도 되는 후보가 된다.
또한 Choosing the State Structure 의 다섯 원칙 중 첫 번째 — “Group related state. If you always update two or more state variables at the same time, consider merging them into a single state variable.” — 가 Q2와 직결된다.
선배 PR 읽기 가이드
선배 PR 읽기 가이드 — 펼쳐보기
이 영역은 정답 PR을 찾는 곳이 아닙니다. 지금 우리 페어가 어디까지 state를 올렸는지, 그 결정 때문에 어떤 비용을 감수하고 있는지를 선배 PR의 대화로 비춰보는 자리입니다. 코드를 먼저 훑기보다, 리뷰어가 멈춰 세운 질문과 작성자의 답변을 먼저 읽어보세요.
PR #439 데이지 — App으로 끌어올렸을 때 생기는 질문
PR #439 데이지(@kimyou1102) · 인라인 리뷰 모음 → — 8기 직전 기수의 PR입니다.
먼저 볼 자리: src/App.tsx:27 인라인 스레드 → 리뷰어가 “CVC 관련 상태는 App이 몰라도 될 것 같습니다. 오히려 알고 있을 때 어떤 단점이 있을까요?” 라고 묻는 지점에서 시작하면 좋습니다.
읽는 관점
- 이 state를 App이 알아야 하는 기능상 이유가 있었는가? 프리뷰, 제출, 에러 표시처럼 두 영역이 동시에 필요한 정보인지 확인합니다.
- App으로 올린 뒤 props가 어디까지 퍼졌는가? 자식 컴포넌트가 단순해진 대신 부모가 무엇을 더 많이 알게 됐는지 봅니다.
- CVC처럼 특정 입력 영역 안에서 끝날 수 있는 state라면, App이 아니라 새 컴포넌트 경계를 만드는 선택지도 있었는지 살핍니다.
페어 대화 포인트
- 우리 코드에서 “일단 App에 둔 state”를 세어봅니다.
- 각 state 옆에 누가 읽는가, 누가 바꾸는가, 같이 바뀌어야 하는 컴포넌트가 둘 이상인가를 적어봅니다.
- “3개 이상 컴포넌트를 지나가니까 전역 상태나 Context가 필요하다”고 판단한 순간이 있었다면, 정말 필요한 공유인지 다시 이야기해봅니다.
PR #361 제이드 — custom hook을 먼저 만들었을 때 생기는 질문
PR #361 제이드(@skiende74) · 인라인 리뷰 모음 → — custom hook 추출을 고민 중이라면 특히 참고하기 좋은 PR입니다.
먼저 볼 자리: src/hooks/useInput.tsx 인라인 → 그리고 src/hooks/useInputs.tsx 인라인 →. 리뷰어가 useInput의 존재 이유와 useInputs로 다시 합친 구조를 질문하는 흐름을 따라가면 됩니다.
읽는 관점
- hook이 실제 중복을 줄이는가, 아니면 state와 handler를 다른 파일로 옮겼을 뿐인가?
useInput처럼 넓은 이름보다useCardNumber처럼 도메인 의미가 남는 이름이 더 읽기 쉬운가?- hook으로 합치기보다 카드번호 컴포넌트, 유효기간 컴포넌트처럼 화면의 의미 단위로 나누는 편이 더 자연스러운가?
페어 대화 포인트
- “훅으로 빼자”는 말이 나왔을 때, 같은 로직이 두 번 이상 반복되고 있는지 먼저 확인합니다.
- 훅 이름만 보고 그 훅의 책임을 설명할 수 있는지 서로 물어봅니다.
- 2단계에서 카드사 선택, 별칭, 비동기 저장 같은 요구사항이 늘어나면 이 hook이 바뀔지, 컴포넌트만 바뀔지 예상해봅니다.
비교하며 읽기
- 데이지 PR은 필요해서 끌어올렸지만 비용을 다시 확인한 사례입니다.
- 제이드 PR은 미리 추출했지만 추상화의 기준을 다시 세운 사례입니다.
- 우리 코드는 둘 중 어디에 가까운지 표시해보세요. 이미 너무 많이 끌어올렸는지, 아직 반복을 보기도 전에 훅을 만들고 있는지 구분하는 것만으로도 다음 리팩터링 방향이 또렷해집니다.
자기 가설이 자식이 state를 자체 보유하는 방향이라면 연관 PR 더 보기에서 state 위치만 매핑된 PR(예: #88 코이, #73 앨버, #195 루루)을 골라, 왜 그 구조가 유지되거나 바뀌었는지 비교해보세요.
추가 읽을거리 — Dan Abramov · Kent C. Dodds
외부 글 모음 — 펼쳐보기
이 주제와 이어지는 외부 글입니다.
Kent C. Dodds — Application State Management with React
- 원문: kentcdodds.com/blog/application-state-management-with-react
- 한 줄 요약: Kent는 “React 자체가 상태 관리 라이브러리” 라는 입장에서 순서를 제안한다 — Local → Lifting → Composition(children prop) → Context → 외부 라이브러리. 공유가 필요하면 먼저 state를 필요한 공통 부모로 올리고, prop drilling이 부담될 때는 컴포넌트 합성을 먼저 검토한 뒤 Context를 고려하라는 흐름이다. 지오·코브 페어의 “3단계 이상 props는 전역?” 질문에는 “단계 수만 보지 말고 필요한 공유 범위부터 확인하라” 는 기준으로 읽는 편이 정확하다.
Dan Abramov — Writing Resilient Components
- 원문: overreacted.io/writing-resilient-components
- 한 줄 요약: 컴포넌트가 부모의 변경에 잘 견디려면 어떤 원칙을 따라야 하는가. 끌어올린 state가 자식의 가정과 어긋날 때의 비용. PR #439 데이지가 App에 끌어올린 뒤 인식한 비용과 이어진다.
Kent C. Dodds — AHA Programming
- 원문: kentcdodds.com/blog/aha-programming
- 한 줄 요약: Avoid Hasty Abstractions — 성급한 추상화를 피하라. 페이먼츠 2단계 프로그래밍 요구사항의 *“처음부터 훅 구조를 설계하지 않는다”*는 이 원칙과 같은 방향으로 읽을 수 있다. PR #361 제이드가 useInput을 처음부터 만들었다가 구조를 다시 생각하게 된 사례와 이어진다.
연관 PR 더 보기
이 주제에 매핑된 1단계 PR 전체 — 펼쳐보기
자기 가설에 가까운 PR을 골라 추가로 비교해보세요.
| PR | 작성자 | 핵심 키워드 |
|---|---|---|
| #6 | @Puterism | state 위치 / 공통 Input/컴포넌트 합성 / 카드사/브랜드 판별 |
| #7 | @dudtjr913 | state 위치 / 객체/배열 state 구조 / 공통 Input/컴포넌트 합성 |
| #9 | @0307kwon | state 위치 / 객체/배열 state 구조 / 카드번호 4분할 / 카드사/브랜드 판별 |
| #11 | @Tanney-102 | state 위치 / 객체/배열 state 구조 / 카드번호 4분할 / 카드사/브랜드 판별 |
| #29 | @swon3210 | state 위치 / 카드번호 4분할 / 공통 Input/컴포넌트 합성 / 카드사/브랜드 판별 |
| #69 | @juunzzi | state 위치 / 공통 Input/컴포넌트 합성 |
| #71 | @DomMorello | state 위치 / 객체/배열 state 구조 / 카드번호 4분할 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 |
| #72 | @jswith | state 위치 / 객체/배열 state 구조 |
| #73 | @al-bur | state 위치 |
| #74 | @LAH1203 | custom hook / 카드번호 4분할 |
| #75 | @intae92 | state 위치 / 카드번호 4분할 |
| #77 | @JUDONGHYEOK | state 위치 / 객체/배열 state 구조 / 카드번호 4분할 / 공통 Input/컴포넌트 합성 |
| #78 | @soyi47 | state 위치 / 객체/배열 state 구조 / 카드번호 4분할 / 공통 Input/컴포넌트 합성 |
| #80 | @compy-ryu | state 위치 / 검증·에러 처리 |
| #82 | @hwangstar156 | state 위치 / 카드번호 4분할 / 카드사/브랜드 판별 |
| #83 | @dayelop | state 위치 |
| #85 | @lokba | state 위치 / 객체/배열 state 구조 / 카드사/브랜드 판별 |
| #88 | @InKyoJeong | state 위치 |
| #89 | @onschan | state 위치 / 객체/배열 state 구조 / 카드번호 4분할 / 검증·에러 처리 |
| #91 | @moonheekim0118 | custom hook / 카드번호 4분할 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 |
| #92 | @nan-noo | state 위치 |
| #94 | @euijinkk | state 위치 / 객체/배열 state 구조 / 카드번호 4분할 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #95 | @KangYunHo1221 | state 위치 / 카드번호 4분할 / 공통 Input/컴포넌트 합성 |
| #96 | @jhy979 | state 위치 / 객체/배열 state 구조 / 카드번호 4분할 / 검증·에러 처리 |
| #97 | @kamwoo | state 위치 / 카드번호 4분할 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 |
| #98 | @byhhh2 | state 위치 / 객체/배열 state 구조 / 공통 Input/컴포넌트 합성 / 카드사/브랜드 판별 |
| #100 | @woose28 | state 위치 / 카드번호 4분할 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 |
| #101 | @sanaandmomo | state 위치 / 카드사/브랜드 판별 |
| #102 | @liswktjs | state 위치 / 카드번호 4분할 / 검증·에러 처리 |
| #104 | @uk960214 | state 위치 / 객체/배열 state 구조 / 검증·에러 처리 |
| #105 | @ronci | custom hook / 카드번호 4분할 / 카드사/브랜드 판별 |
| #189 | @nlom0218 | state 위치 / 객체/배열 state 구조 / 카드번호 4분할 / 검증·에러 처리 |
| #190 | @dladncks1217 | state 위치 / 객체/배열 state 구조 / 카드번호 4분할 / 카드사/브랜드 판별 |
| #192 | @semnil5202 | state 위치 |
| #193 | @suyoungj | state 위치 / 객체/배열 state 구조 / 카드번호 4분할 |
| #195 | @hafnium1923 | state 위치 |
| #196 | @Leejin-Yang | state 위치 |
| #197 | @xodms0309 | state 위치 / 카드번호 4분할 / 공통 Input/컴포넌트 합성 |
| #198 | @ashleysyheo | state 위치 / 객체/배열 state 구조 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 |
| #199 | @guridaek | state 위치 / 카드번호 4분할 |
| #201 | @HyeryongChoi | state 위치 / 객체/배열 state 구조 / 카드번호 4분할 / 공통 Input/컴포넌트 합성 |
| #203 | @nangkyeonglim | state 위치 / 객체/배열 state 구조 / 공통 Input/컴포넌트 합성 |
| #204 | @NaveOWO | state 위치 / 객체/배열 state 구조 / 공통 Input/컴포넌트 합성 |
| #205 | @2yunseong | state 위치 / 객체/배열 state 구조 / 카드번호 4분할 / 공통 Input/컴포넌트 합성 |
| #206 | @turtle601 | custom hook / 객체/배열 state 구조 / 검증·에러 처리 |
| #207 | @inyeong-kang | custom hook / 객체/배열 state 구조 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 |
| #208 | @D0Dam | state 위치 / 객체/배열 state 구조 / 공통 Input/컴포넌트 합성 |
| #209 | @jiwonh423 | state 위치 / 공통 Input/컴포넌트 합성 |
| #211 | @jariita | state 위치 / 객체/배열 state 구조 |
| #212 | @jw-r | state 위치 / 객체/배열 state 구조 / 카드번호 4분할 |
| #213 | @wzrabbit | state 위치 / 객체/배열 state 구조 / 카드번호 4분할 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 |
| #214 | @n0eyes | custom hook / 객체/배열 state 구조 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 |
| #215 | @Dahyeeee | state 위치 / 객체/배열 state 구조 / 공통 Input/컴포넌트 합성 |
| #216 | @yeopto | state 위치 / 검증·에러 처리 |
| #220 | @ukkodeveloper | state 위치 / 객체/배열 state 구조 |
| #223 | @geuntaek1013 | state 위치 / 검증·에러 처리 |
| #225 | @woo-jk | state 위치 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 |
| #226 | @regularPark | state 위치 / 객체/배열 state 구조 / 공통 Input/컴포넌트 합성 |
| #228 | @hozzijeong | state 위치 / 객체/배열 state 구조 |
| #230 | @gyeongza | state 위치 / 객체/배열 state 구조 / 카드번호 4분할 / 공통 Input/컴포넌트 합성 |
| #232 | @Gilpop8663 | custom hook / 객체/배열 state 구조 / 카드번호 4분할 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 |
| #233 | @sh981013s | state 위치 / 객체/배열 state 구조 / 공통 Input/컴포넌트 합성 |
| #234 | @yogjin | state 위치 / 객체/배열 state 구조 / 공통 Input/컴포넌트 합성 |
| #331 | @anttiey | state 위치 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #334 | @vi-wolhwa | state 위치 / 객체/배열 state 구조 / 카드번호 4분할 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #337 | @jinhokim98 | custom hook / 객체/배열 state 구조 / 카드번호 4분할 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #338 | @Largopie | custom hook / 객체/배열 state 구조 / 카드번호 4분할 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #339 | @Yoonkyoungme | state 위치 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 |
| #341 | @simorimi | state 위치 / 객체/배열 state 구조 / 카드번호 4분할 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #343 | @brgndyy | custom hook / 객체/배열 state 구조 / 카드번호 4분할 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #344 | @soi-ha | state 위치 / 객체/배열 state 구조 / 카드번호 4분할 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #346 | @chlwlstlf | state 위치 / 카드사/브랜드 판별 |
| #348 | @pp449 | state 위치 / 객체/배열 state 구조 / 카드번호 4분할 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #349 | @BadaHertz52 | state 위치 / 객체/배열 state 구조 / 카드번호 4분할 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 |
| #352 | @Hain-tain | state 위치 |
| #353 | @ooherin | custom hook / 객체/배열 state 구조 / 카드번호 4분할 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 |
| #354 | @hwinkr | state 위치 / 객체/배열 state 구조 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #357 | @00kang | state 위치 / 객체/배열 state 구조 / 카드번호 4분할 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #358 | @dle234 | state 위치 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #359 | @rbgksqkr | custom hook / 카드번호 4분할 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #361 | @skiende74 (대표) | custom hook / 객체/배열 state 구조 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #363 | @chysis | state 위치 / 공통 Input/컴포넌트 합성 / 카드사/브랜드 판별 |
| #366 | @Parkhanyoung | custom hook / 카드번호 4분할 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #367 | @ss0526100 | custom hook / 객체/배열 state 구조 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #409 | @keemsebin | state 위치 / 객체/배열 state 구조 / 카드사/브랜드 판별 |
| #411 | @AHHYUNJU | state 위치 / 카드번호 4분할 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #412 | @mlnwns | state 위치 / 객체/배열 state 구조 / 카드번호 4분할 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #413 | @Beomtae | custom hook / 카드번호 4분할 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 |
| #414 | @ohgus | custom hook / 객체/배열 state 구조 / 카드번호 4분할 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 |
| #416 | @jeongyou | state 위치 / 객체/배열 state 구조 / 카드번호 4분할 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 |
| #417 | @sooyeoniya | custom hook / 카드번호 4분할 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #418 | @MinSungJe | state 위치 / 객체/배열 state 구조 / 카드번호 4분할 / 카드사/브랜드 판별 |
| #420 | @ShinjungOh | state 위치 / 카드번호 4분할 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #421 | @ExceptAnyone | state 위치 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #423 | @yeji0214 | state 위치 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #425 | @Db0111 | state 위치 / 객체/배열 state 구조 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #426 | @rosielsh | state 위치 / 객체/배열 state 구조 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 |
| #428 | @kaori-killer | custom hook / 검증·에러 처리 |
| #430 | @yeongipark | state 위치 / 객체/배열 state 구조 / 카드번호 4분할 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #431 | @eunwoo-levi | state 위치 / 객체/배열 state 구조 / 검증·에러 처리 |
| #433 | @eunsoA | state 위치 / 객체/배열 state 구조 / 카드번호 4분할 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #434 | @minji2219 | state 위치 / 카드번호 4분할 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #435 | @dev-dino22 | state 위치 / 객체/배열 state 구조 / 카드번호 4분할 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #439 | @kimyou1102 (대표) | custom hook / 객체/배열 state 구조 / 카드번호 4분할 / 공통 Input/컴포넌트 합성 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #440 | @dlsxjzld | custom hook / 객체/배열 state 구조 / 카드번호 4분할 / 검증·에러 처리 |
| #442 | @jaeyoung-kwon | state 위치 / 공통 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/컴포넌트 합성 / 검증·에러 처리 / 카드사/브랜드 판별 |
| #450 | @Daeun-100 | state 위치 / 검증·에러 처리 |
| #452 | @kaori-killer | state 위치 / 검증·에러 처리 |
| #453 | @ExceptAnyone | state 위치 / 검증·에러 처리 / 카드사/브랜드 판별 |
195개 전체 인덱스(다른 주제 포함)는 부록에서 확인할 수 있습니다.





