Next.js
React 상태 업데이트가 늦게 보일 때 렌더링 경계를 나눈 기록
버튼을 눌렀는데 화면 일부만 늦게 바뀌던 문제를 재현하고, 상태 위치와 key를 분리해 확인한 기록입니다.
버튼은 눌렸는데 숫자가 그대로였다
좋아요 버튼을 누르면 서버 요청은 200을 반환했는데 목록 카드의 숫자는 한 박자 늦게 바뀌었다. 처음에는 API 응답을 의심했지만, 개발자 도구의 Network 탭에는 이미 새 값이 찍혀 있었다. 실제 문제는 카드 전체를 감싸는 컴포넌트에서 post 객체를 한 번만 만들어 내려보내고, 자식 컴포넌트의 로컬 상태는 요청 완료 시 갱신하지 않은 데 있었다.
재현할 때는 목록을 세 개로 줄이고, 응답에 500ms 지연을 넣었다.
const [liked, setLiked] = useState(initialLiked)
const [count, setCount] = useState(initialCount)
async function toggleLike() {
const next = !liked
setLiked(next)
const result = await fetch(`/api/posts/${id}/like`, {
method: 'POST',
body: JSON.stringify({ liked: next }),
})
const data = await result.json()
setCount(data.count)
}
상태를 어디에서 소유할지 다시 정했다
처음 수정안은 부모가 key={post.updatedAt}를 바꿔 자식을 강제로 다시 마운트하는 방식이었다. 화면은 맞아 보였지만 포커스와 입력값까지 초기화되는 부작용이 생겼다. 그래서 key는 목록 순서를 안정적으로 식별하는 용도로만 남기고, 좋아요 수와 선택 상태의 소유자를 나눴다.
- 서버에서 다시 받아야 하는 값: 부모 목록의
post.count - 클릭 직후 즉시 보여줄 값: 카드 내부의
liked,count - 서버 결과가 실패했을 때: 이전 두 값을 함께 복원
실패 경로를 먼저 테스트했다
낙관적 업데이트만 넣으면 빠르게 반응하지만 요청 실패 시 숫자가 틀어진다. 테스트에서는 성공뿐 아니라 500 응답, 연속 클릭, 컴포넌트 unmount 상황을 각각 확인했다.
try {
const data = await saveLike(next)
setCount(data.count)
} catch {
setLiked(previousLiked)
setCount(previousCount)
}
이번에 고정한 기준은 간단하다. API 응답이 정상이어도 화면 상태를 자동으로 바꿔 주지는 않는다. 어떤 값이 서버의 진실이고 어떤 값이 상호작용 중 임시 값인지 먼저 적어 두면, 억지로 remount하는 수정은 피할 수 있다.
확인 목록
- Network 응답과 화면에 표시한 상태를 따로 비교했는가
- key를 상태 초기화 도구로 사용하고 있지 않은가
- 실패 시 낙관적 상태를 되돌리는가
- 빠른 연속 클릭을 한 번의 상태 전이로 정의했는가