
안녕하세요. 코딩하는 Jay입니다.
블로그를 시작한 지 엊그제 같은데 어느덧 110번째 개발 포스팅을 맞이하게 되었습니다. 기념비적인 110번째 글인 만큼, 오늘은 최근 Next.js 15와 함께 프론트엔드 실무 생태계의 중심에 선 React 19의 핵심 신규 훅 2가지(useActionState, useOptimistic)를 다뤄보려 합니다.
프론트엔드 개발자라면 누구나 한 번쯤 폼(Form)을 다루며 고통받아본 경험이 있으실 겁니다. 사용자 입력을 받기 위해 수많은 useState를 선언하고, 제출 중 로딩 스피너를 띄우기 위해 isLoading 플래그를 관리하며, 에러를 잡기 위해 장황한 try-catch 블록을 작성해야 했죠.
React 19는 이러한 보일러플레이트 지옥을 네이티브 차원에서 종식시켰습니다. 오늘은 실무 코드에서 이 두 훅을 어떻게 적용하는지 명확한 예제와 함께 정리해 드립니다.
한눈에 보는 폼 상태 관리 변화
| 항목 | React 18 이하 방식 | React 19 신규 방식 |
|---|---|---|
| 상태 관리 | useState (값, 로딩, 에러 각각 분리 선언) |
useActionState 하나로 통합 |
| 로딩 처리 | 수동 setIsLoading(true) 관리 |
isPending 불리언 값 자동 제공 |
| 낙관적 UI | 복잡한 수동 롤백 및 캐시 변이 로직 | useOptimistic 훅으로 선언적 처리 |
| 폼 제출 | onSubmit 이벤트 막고 비동기 핸들러 연결 |
직관적 선언 |
1. `useActionState`: 비동기 액션과 상태의 완전한 통합
useActionState는 비동기 작업(서버 액션, API 호출)의 결과에 따라 컴포넌트 상태를 업데이트해 주는 React 19의 핵심 훅입니다. 비동기 함수를 전달하면 [최신 상태, 액션 디스패치 함수, 진행 여부(isPending)]를 튜플 형태로 반환합니다.
실전 예제: 회원 프로필 닉네임 변경 폼
'use client';
import { useActionState } from 'react';
import { updateUsername } from '@/actions/user';
export default function ProfileForm({ initialName }: { initialName: string }) {
// [현재 상태, 실행 액션 함수, 로딩 상태]
const [state, formAction, isPending] = useActionState(
async (previousState: any, formData: FormData) => {
const newName = formData.get('username') as string;
const result = await updateUsername(newName);
if (!result.success) {
return { error: result.message, name: previousState.name };
}
return { error: null, name: newName };
},
{ error: null, name: initialName }
);
return (
<form action={formAction} className="space-y-4">
<input
type="text"
name="username"
defaultValue={state.name}
placeholder="새 닉네임 입력"
className="border p-2 rounded"
/>
<button
type="submit"
disabled={isPending}
className="bg-blue-600 text-white px-4 py-2 rounded"
>
{isPending ? '저장 중...' : '변경 완료'}
</button>
{state.error && <p className="text-red-500">{state.error}</p>}
</form>
);
}
- 코드 다이어트: 별도의
isLoading,errorMessage,name상태를 줄줄이 만들 필요 없이 하나의 액션 함수에서 에러와 결과를 깔끔하게 제어할 수 있습니다. - 자동 펜딩 감지: 버튼 비활성화(
disabled={isPending})나 로딩 텍스트 노출이 자연스럽게 연동됩니다.
2. `useOptimistic`: 지연 없는 반응형 UX의 완성
사용자가 게시글의 '좋아요' 버튼을 누르거나 장바구니에 아이템을 담을 때, 서버 응답이 올 때까지 0.5초 동안 아무 반응이 없다면 사용자는 답답함을 느낍니다.
useOptimistic은 서버 응답이 성공할 것으로 "낙관(Optimistic)"하고, 화면의 UI를 즉시 먼저 갱신해 버리는 훅입니다. 만약 서버에서 에러가 발생하면 React가 알아서 원래 상태로 롤백해 줍니다.
실전 예제: 소셜 게시글 좋아요 토글
'use client';
import { useOptimistic, startTransition } from 'react';
import { toggleLikeAction } from '@/actions/post';
export default function LikeButton({
postId,
initialLikes,
initialLiked,
}: {
postId: string;
initialLikes: number;
initialLiked: boolean;
}) {
// 낙관적 상태 선언
const [optimisticState, setOptimisticState] = useOptimistic(
{ likes: initialLikes, liked: initialLiked },
(state, update: boolean) => ({
likes: update ? state.likes + 1 : state.likes - 1,
liked: update,
})
);
const handleLike = async () => {
const nextLiked = !optimisticState.liked;
// 1. 화면 UI를 즉각적으로 변경 (사용자에게 0ms 체감 제공)
startTransition(async () => {
setOptimisticState(nextLiked);
// 2. 백그라운드에서 실제 서버 통신 수행
await toggleLikeAction(postId, nextLiked);
});
};
return (
<button
onClick={handleLike}
className={`px-4 py-2 rounded-full font-bold ${
optimisticState.liked ? 'bg-red-500 text-white' : 'bg-gray-200'
}`}
>
❤️ {optimisticState.likes}
</button>
);
}
- 사용자가 버튼을 클릭하는 즉시 하트 색상과 숫자가 1 증가합니다.
- 서버 요청이 진행되는 도중에도 UI가 끊김 없이 즉각 반응하며, 혹시 네트워크 에러가 발생하면 자동으로 이전 카운트로 되돌아갑니다.
💡 실무 적용 시 핵심 체크포인트
useActionState는useFormState의 후속: React Canary 시절 사용되던useFormState는 이제 deprecated되었으므로, Next.js 15에서는 반드시useActionState를 import해야 합니다.- 트랜지션 래핑 필수:
useOptimistic을 단독으로 dispatch할 때는 반드시startTransition블록 내부에서 호출해야 비동기 렌더링 우선순위가 올바르게 작동합니다. - 접근성(a11y) 향상:
isPending상태를 활용해 스크린 리더용aria-busy="true"속성을 추가해 주면 접근성 점수까지 손쉽게 확보할 수 있습니다.
🚀 마치며
React 19는 화려한 신기술보다는 "개발자가 매일 작성하는 지루한 코드를 얼마나 우아하게 줄여줄 수 있는가"에 초점을 맞춘 성숙한 버전입니다.
특히 폼 처리와 낙관적 UI 업데이트는 웹 개발의 필수 요소인 만큼, 이번에 소개해 드린 두 훅을 프로젝트에 적극 도입해 보시길 강력히 권장합니다!
'React' 카테고리의 다른 글
| [Next.js 15] Turbopack 정식 지원과 React 19 호환: 개발자가 알아야 할 핵심 변경점 및 마이그레이션 가이드 (0) | 2026.10.29 |
|---|---|
| [React] react-markdown 심화: 수학 수식(KaTeX) · 코드 하이라이트 · 유용한 플러그인 총정리 (0) | 2026.06.04 |
| Zustand를 활용한 React 상태 관리 (0) | 2024.12.31 |
| React 환경에서 Markdown 출력하기 (react-markdown) (0) | 2024.04.22 |
| Next.js v14 (0) | 2023.10.31 |