들어가며
몇 달 전, React Query로 낙관적 UI를 구현하면서 꽤나 고민했던 기억이 있습니다.
그런데 React 19에서 소개된 useOptimistic
을 보고 "아, 이거였구나!" 하는 깨달음을 얻었어요.
오늘은 제가 겪었던 문제를 React 19의 새로운 훅으로 어떻게 더 우아하게 해결할 수 있는지 이야기해보려 합니다! 😊
문제 상황: 느린 좋아요 버튼 🐢
인스타그램 스타일의 좋아요 기능을 구현하면서 마주쳤던 문제는 다음과 같았어요:
- 서버 응답을 기다리느라 버튼 반응이 느림 (1-2초)
- 연속 클릭 시 처리가 까다로움
- 사용자 경험이 매끄럽지 않음
useOptimistic으로 해결하기 ⚡
1. 기본 구현
function LikeButton({ postId }: { postId: string }) {
const [optimisticLikes, addOptimisticLike] = useOptimistic(
{ count: 0, isLiked: false },
(state, newLike: boolean) => ({
count: state.count + (newLike ? 1 : -1),
isLiked: newLike
})
);
async function handleLike() {
// 낙관적으로 상태 업데이트
addOptimisticLike(!optimisticLikes.isLiked);
try {
await toggleLike(postId);
} catch (error) {
// 실패 시 자동으로 원래 상태로 복구!
console.error('좋아요 실패:', error);
}
}
return (
<button
onClick={handleLike}
className={`like-button ${optimisticLikes.isLiked ? 'active' : ''}`}
>
<HeartIcon />
<span>{optimisticLikes.count}</span>
</button>
);
}
2. 서버 액션과 함께 사용
'use client';
function LikeButton({ postId }: { postId: string }) {
const [optimisticLikes, addOptimisticLike] = useOptimistic(
{ count: 0, isLiked: false },
(state, newLike: boolean) => ({
count: state.count + (newLike ? 1 : -1),
isLiked: newLike
})
);
// 서버 액션 정의
async function toggleLikeAction(postId: string) {
'use server';
// 서버에서 좋아요 토글 처리
return await db.likes.toggle(postId);
}
return (
<form action={async () => {
addOptimisticLike(!optimisticLikes.isLiked);
await toggleLikeAction(postId);
}}>
<button type="submit">
<HeartIcon filled={optimisticLikes.isLiked} />
<span>{optimisticLikes.count}</span>
</button>
</form>
);
}
React Query vs useOptimistic 🤔
이전에는 React Query로 이런 식으로 구현했었죠:
const { mutate } = useMutation({
mutationFn: toggleLike,
onMutate: async () => {
await queryClient.cancelQueries(['likes', postId]);
const previous = queryClient.getQueryData(['likes', postId]);
queryClient.setQueryData(['likes', postId], old => ({
...old,
isLiked: !old.isLiked,
count: old.count + (old.isLiked ? -1 : 1)
}));
return { previous };
},
onError: (err, variables, context) => {
queryClient.setQueryData(['likes', postId], context.previous);
}
});
하지만 useOptimistic
을 사용하면:
- 코드가 훨씬 간결해짐
- 롤백 처리가 자동으로 됨
- 서버 상태 관리와 UI 상태 관리가 깔끔하게 분리됨
고급 활용 예시 🚀
1. 애니메이션과 함께 사용
function LikeButton() {
const [optimisticLikes, addOptimisticLike] = useOptimistic(
{ count: 0, isLiked: false },
(state, newLike: boolean) => ({
count: state.count + (newLike ? 1 : -1),
isLiked: newLike
})
);
return (
<button onClick={handleLike}>
<motion.div
animate={{
scale: optimisticLikes.isLiked ? [1, 1.2, 1] : 1
}}
>
<HeartIcon />
</motion.div>
<AnimatePresence>
<motion.span
key={optimisticLikes.count}
initial={{ y: -10, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
>
{optimisticLikes.count}
</motion.span>
</AnimatePresence>
</button>
);
}
2. 여러 상태 동시 관리
function SocialActions() {
const [optimisticState, addOptimisticAction] = useOptimistic(
{ likes: 0, bookmarks: 0, isLiked: false, isBookmarked: false },
(state, action: 'like' | 'bookmark') => {
switch (action) {
case 'like':
return {
...state,
likes: state.likes + (state.isLiked ? -1 : 1),
isLiked: !state.isLiked
};
case 'bookmark':
return {
...state,
bookmarks: state.bookmarks + (state.isBookmarked ? -1 : 1),
isBookmarked: !state.isBookmarked
};
}
}
);
return (
<div className="social-actions">
<LikeButton
state={optimisticState}
onAction={() => addOptimisticAction('like')}
/>
<BookmarkButton
state={optimisticState}
onAction={() => addOptimisticAction('bookmark')}
/>
</div>
);
}
주의사항과 팁 ⚠️
- 상태 설계 주의
// 🚫 이렇게 하지 마세요 const [optimistic, addOptimistic] = useOptimistic<number>( 0, (state) => state + 1 );
// ✅ 이렇게 하세요
const [optimistic, addOptimistic] = useOptimistic(
{ value: 0, lastUpdated: null },
(state) => ({
value: state.value + 1,
lastUpdated: new Date()
})
);
2. **에러 처리는 꼼꼼하게**
```tsx
try {
addOptimisticLike(true);
await toggleLike();
} catch (error) {
toast.error('좋아요 처리 중 오류가 발생했습니다');
// useOptimistic이 자동으로 상태를 복구해줍니다!
}
마치며
React 19의 useOptimistic
은 정말 게임체인저라고 생각합니다.
이전에는 복잡한 코드로 구현했던 낙관적 UI를 이제는 훨씬 더 우아하게 만들 수 있게 되었네요.
특히 서버 컴포넌트와 함께 사용할 때 그 진가를 발휘하는 것 같습니다.
React가 이런 식으로 발전해나가는 걸 보면서, 프론트엔드 개발이 점점 더 재미있어지는 것 같아요!
즐거운 코딩 되세요! 🚀
'React.js' 카테고리의 다른 글
[React 19] useFormStatus로 더 똑똑한 폼 UI 만들기 (0) | 2025.03.17 |
---|---|
[React 19] useActionState로 서버 상태 관리하기 (0) | 2025.03.17 |
[React 19] useTransition으로 더 부드러운 UI 구현하기 (0) | 2025.03.17 |
[React] 렌더링 최적화하기 - React.memo 현명하게 사용하기 (0) | 2025.03.17 |
[React] 웹 페이지 복귀, 이탈 탐지 (가시성) (0) | 2023.06.28 |