사용자가 삭제 버튼을 눌렀을 때, 서버의 응답을 기다리지 않고 화면에서 먼저 지워버려 앱이 즉각적으로 반응하게 만드는 기법임
비동기 통신의 지연 시간(Latency)을 사용자 경험(UX)으로 극복하는 고급 기술이다.
데이터 삭제는 단순히 명령을 보내는 것이 아니라, UI와 서버 데이터를 동기화하는 섬세한 과정이 필요하다.
❖ 구현 프로세스:
1. 화면 선제 업데이트: setUserPlaces를 호출하여 클릭한 항목을 리스트에서 즉시 제거한다.
2. 서버 통신 수행: fetch 요청을 통해 서버에 삭제 명령을 보낸다.
3. 에러 핸들링 (복구): 만약 서버 삭제가 실패하면, 화면에서 지웠던 데이터를 다시 복구시킨다.
사용자는 "내 명령이 즉시 실행되었다"고 느끼며, 앱이 매우 빠르다는 인상을 받게 된다.
const handleRemovePlace = useCallback(async function handleRemovePlace() {
// 1. 낙관적 업데이트: UI에서 먼저 제거
setUserPlaces((prevPickedPlaces) =>
prevPickedPlaces.filter((place) => place.id !== selectedPlace.current.id)
);
try {
// 2. 서버에 실제 삭제 요청
await updateUserPlaces(
userPlaces.filter((place) => place.id !== selectedPlace.current.id)
);
} catch (error) {
// 3. 실패 시 복구 로직: 이전 상태로 되돌림
setUserPlaces(userPlaces);
setErrorUpdatingPlaces({
message: error.message || '삭제에 실패했습니다.',
});
}
}, [userPlaces]);
- 배열 필터링:
filter메서드를 사용하여 선택된 ID를 제외한 새로운 배열을 생성하고 상태를 업데이트함- 비동기 요청:
await를 사용하여 서버의 응답을 기다리지만, 사용자는 이미 지워진 화면을 보고 있음- 롤백(Rollback): 네트워크 단절 등으로 서버 작업이 실패하면
catch문에서 기존userPlaces를 다시 세팅하여 항목을 화면에 다시 나타나게 함
이 삭제 함수는 보통 자식 컴포넌트(모달이나 버튼)로 전달되는 경우가 많다.
❖ 최적화 이유:
useCallback을 쓰지 않으면 부모가 리렌더링될 때마다 삭제 함수가 새로 만들어진다.memo 처리된 경우)까지 다시 그려지게 되므로, 이를 방지하기 위해 주소를 박제한다.userPlaces를 넣어 최신 데이터 상태를 유지하도록 설계한다.