본문 바로가기
TECH

useOptimistic: React 19의 새로운 기능 🆕

by Stella-Park 2025. 12. 17.
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