[리액트 상태 관리 1편] 혹시 모든 데이터를 `useState`로 만들고 있나?
kwangmook.dev·2026. 05. 31.·2 min read
1. 상태도 소속이 있다: 지역, 전역, 서버
상태 관리를 시작할 때 먼저 한 일은 “이 값이 어디 소속인가?”를 구분하는 것이었다.
이걸 안 하면 로컬 상태로 버틸 걸 전역으로 올리거나, 서버 상태를 클라이언트 상태처럼 다루는 실수를 반복하게 됐다.
- 지역 상태(Local State)
- 예: 인풋 값, 모달 열림 여부, 탭 선택값
- 보통
useState,useReducer로 충분
- 전역 상태(Global State)
- 예: 로그인 유저 정보, 테마, 권한, 공통 UI 상태
- 여러 컴포넌트가 공유해야 해서 props만으로는 한계
- 서버 상태(Server State)
- 예: 게시글 목록, 댓글, 로딩/에러/재시도
- 원본은 서버에 있고, 클라이언트는 조회/캐시/동기화가 핵심
핵심은 “모든 상태를 같은 방식으로 관리하지 않는다” 이다.
2. 실수 1 : 파생값까지 상태로 만들기
가장 많이 하는 실수의 예시
- props로 받은 값
- 기존 state로 계산 가능한 값
이런 것까지 별도 state로 또 만들고 useEffect로 동기화한다.
const [email, setEmail] = useState(initialEmail);
useEffect(() => {
setEmail(initialEmail);
}, [initialEmail]);
처음엔 안전해 보이는데, 나중에는 “진짜 원본이 뭐지?”가 흐려진다.
실제로 값 출처가 2개가 되면 디버깅 난이도가 확 올라간다.
여기서 중요했던 개념이 SSOT였다.
- SSOT(Single Source of Truth): 데이터의 진실 공급원은 하나만 둔다.
2-1. 해결할 때 기준
- 기존 값으로 계산 가능한가?
- 가능하면 state로 두지 않고 렌더 시 계산
- 부모가 이미 소유한 데이터인가?
- 자식에서 복사본 state 만들지 않고 props로 사용
- 여러 곳에서 수정해야 하는가?
- 상태 소유자(owner)를 명확히 정해서 끌어올리기
결국 “상태를 추가하기 전에, 진짜 필요한지”를 먼저 확인하는 습관이 중요하다.
3. 상태가 서로 엮이면 useReducer가 편했다
useState는 단일 값 관리에는 가볍고 좋다.
문제는 상태끼리 규칙이 생길 때였다.
예를 들면 필터 UI에서:
- 필터가 바뀌면 페이지는 0으로 초기화
- 정렬이 바뀌면 선택된 항목은 해제
- 날짜 범위가 비정상이면 자동 보정
이런 규칙이 생기면 setState가 여러 군데 흩어지고 누락 버그가 생기기 쉽다.
이때 useReducer로 바꾸니까 업데이트 규칙을 한 곳에 모을 수 있다.
type State = {
keyword: string;
page: number;
rating: number | null;
};
type Action =
| { type: "setKeyword"; payload: string }
| { type: "setRating"; payload: number | null }
| { type: "goPage"; payload: number };
function reducer(state: State, action: Action): State {
switch (action.type) {
case "setKeyword":
return { ...state, keyword: action.payload, page: 0 };
case "setRating":
return { ...state, rating: action.payload, page: 0 };
case "goPage":
return { ...state, page: action.payload };
default:
return state;
}
}
이전/다음 포스트
댓글 남기기
댓글 0개
첫 댓글을 남겨보세요.