728x90
React 19에서 새로 추가된 기능 중 useOptimistic이 있다.
useOptimistic는 낙관전 UI 업데이트 (Optimistic UI Update)를 쉽게 구현할 수 있는 훅이다.
이 기능은 서버 응답을 기다리지 않고 UI를 먼저 업데이트하여 사용자 경험 (UX)을 개선하는 데 초점을 맞춘다.
예를 들면, 좋아요 버튼을 클릭했을 때 서버 응답이 느리더라도 UI는 즉시 반응하도록 하는 방식이다.
useOptimistic란?
목적: 비동기 작업 (예: 네트워크 요청)이 완료되기 전에 UI를 예상 결과로 업데이트
자동 롤백: 요청이 실패하면 낙관적 상태는 원래 상태로 자동 복구
const [optimisticState, addOptimistic] = useOptimistic(
state, // 실제 상태
(currentState, optimisticValue) => {
// 낙관적 상태를 계산하는 함수
return newState;
}
);
매게변수
- state: 기본 상태 값
- updateFn(currentState, optimisticValue): 낙관적 상태를 계산하는 순수 함수
반환값
- optimisticState: 현재 낙관적 상태
- addOptimistic(value): 낙관적 업데이트를 트리거하는 함수
사용 예제
1. 메시지 전송 UI
import { useOptimistic, useState, useRef, startTransition } from 'react'
function Thread({ messages, sendMessageAction }) {
const formRef = useRef()
function formAction(formData) {
addOptimisticMessage(formData.get('message'))
formRef.current.reset()
startTransition(async () => {
await sendMessageAction(formData)
})
}
const [optimisticMessages, addOptimisticMessage] = useOptimistic(
messages,
(state, newMessage) => [
{ text: newMessage, sending: true },
...state
]
)
return (
<>
<form action={formAction} ref={formRef}>
<input
type="text"
name="message"
placeholder="Hello!"
/>
<button type="submit">Send</button>
</form>
{optimisticMessages.map((message, index) => (
<div key={index}>
{message.text}
{message.sending && <small>(Sending...)</small>}
</div>
))}
</>
)
}
- addOptimisticMessage로 메시지를 즉시 UI에 추가
- 서버 응답이 늦어도 사용자에게는 빠른 반응 제공
- 실패 시 자동 롤백
728x90
2. 좋아요 버튼
function LikeButton({ postId }) {
const [optimisticLikes, addOptimisticLike] = useOptimistic(
{ count: 0, isLiked: false },
(state, newLike) => ({
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}>
❤️ {optimisticLikes.count}
</button>
)
}
- 코드가 간결
- 실패 시 자동 롤백
- React Query보다 구현이 단순
3. 카운터 + 실패 롤백
'use client'
import { useOptimistic, useState, useTransition } from 'react'
export default function Counter() {
const [count, setCount] = useState(0) // 실제 서버 동기화된 값
const [error, setError] = useState<string | null>(null) // 에러 메시지
const [isPending, startTransition] = useTransition() // UI 전환 상태 관리
const [optimisticCount, addOptimisticCount] = useOptimistic<number, number>(
count,
(state, increment) => state + increment
)
const handleClick = () => {
startTransition(async () => {
setError(null)
addOptimisticCount(1) // 낙관적 업데이트
try {
await new Promise<void>((res) => setTimeout(res, 1500)) // 서버 요청 시뮬레이션
const isSuccess = Math.random() > 0.5 // 성공/실패 랜덤
if (!isSuccess) throw new Error('increment failed!')
setCount((prev) => prev + 1) // 실제 상태 업데이트
} catch (err) {
setError((err as Error).message) // 실패 시 에러 표시
}
})
}
return (
<div>
<p>count: {count}</p>
<p>optimisticCount: {optimisticCount}</p>
<button onClick={handleClick} disabled={isPending}>
{isPending ? 'Loading...' : 'Increment'}
</button>
{error && <p style={{ color: 'red' }}>{error}</p>}
</div>
)
}
코드 추가 설명
- addOptimisticCount(1): 낙관적 업데이트 실행 -> UI에서 즉시 반영
- (State, increment) => state + increment: 낙관적 상태 계산 로직
startTransition
- 비동기 작업 중 UI를 부드럽게 유지
- isPending으로 상태 관리
서버 요청
- 1.5초 후 성공/실패 랜덤
- 성공 -> setCount로 실제 상태 업데이트
- 실패 -> error 상태 업데이트
왜 useState 대신 쓸까?
useOptimistic은 React의 동시 렌더링 (Concurrent Rendering)과 트랜지션 (Transition) 기능에 최적화되어 있다. 때문에 단순한 상태 업데이트만으로는 구현하기 어려운 정교한 UI 동기화를 도와주고, 작업 실패 시 자동 롤백도 쉽게 저리할 수 있다.
언제 사용하면 좋을까?
- 좋아요 버튼, 댓글 추가, 폼 제출 등 UX가 중요한 가벼운 작업
- 중요한 데이터 (결제, 계정 변경 등)에는 신중히 사용
이상 스텔라였습니다 ✍🏻

728x90
'TECH' 카테고리의 다른 글
| 검색 파라미터 🔍 (0) | 2025.12.15 |
|---|---|
| 5가지 노드 버전 관리자 비교 (0) | 2025.12.04 |
| 아이콘 현지화 (localization) (0) | 2025.12.02 |
| 논리 할당 연산자 (0) | 2025.11.20 |
| Vite+ (0) | 2025.11.19 |