useOptimistic으로 즉각적인 피드백 제공
액션이 백그라운드에서 처리되는 동안 좋아요, 댓글 및 토글에 낙관적 UI 업데이트를 적용하는 방법을 배웁니다.
useOptimistic으로 즉각적인 피드백 제공은(는) CoddyKit의 무료 Next.js 15 Fullstack (App Router + Server Actions) 강의입니다. 이것은 4개 중 4번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 Next.js 15 Fullstack (App Router + Server Actions) 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. Next.js 15 Fullstack (App Router + Server Actions) 강의에는 총 4개의 강의가 포함되어 있습니다.
이 강의의 일부는 아직 번역되지 않았으며 영어로 표시됩니다.
The Latency Problem
When a user clicks a like button, the change has to travel to the server and back before the UI reflects it. On a slow network that delay feels broken.
The fix is optimistic UI: assume the action will succeed, update the screen instantly, and reconcile with the server once it responds. If the server fails, you roll back automatically.
React 19 (shipped with Next.js 15) gives you a dedicated hook for this: useOptimistic.
What useOptimistic Returns
useOptimistic takes your real state and an update function, and returns a temporary optimistic value plus a function to apply optimistic changes.
optimisticState— what you render right nowaddOptimistic(value)— queue an optimistic change
The signature is useOptimistic(state, (currentState, optimisticValue) => newState). The reducer-style function merges the optimistic value into the current state.
const [optimisticState, addOptimistic] = useOptimistic(
state,
(currentState, optimisticValue) => {
// return the new state to display optimistically
return { ...currentState, ...optimisticValue };
}
);A Like Button: The Server Action
First, the real work happens in a Server Action. It runs on the server, mutates the database, and revalidates the cache.
Notice the "use server" directive and the revalidatePath call so other parts of the app see the fresh count.
"use server";
import { revalidatePath } from "next/cache";
import { db } from "@/lib/db";
export async function toggleLike(postId: string, liked: boolean) {
await db.post.update({
where: { id: postId },
data: { likes: { increment: liked ? 1 : -1 } },
});
revalidatePath("/feed");
}Wiring useOptimistic to the Button
In a Client Component, we hold the optimistic like state. When clicked, we call addOptimistic first (instant UI), then await the Server Action.
If the action throws, React automatically discards the optimistic value and reverts to the real likes prop.
"use client";
import { useOptimistic, useTransition } from "react";
import { toggleLike } from "./actions";
export function LikeButton({ postId, likes, liked }: {
postId: string; likes: number; liked: boolean;
}) {
const [optimistic, addOptimistic] = useOptimistic(
{ likes, liked },
(state, next: boolean) => ({
liked: next,
likes: state.likes + (next ? 1 : -1),
})
);
const [, startTransition] = useTransition();
return (
<button
onClick={() =>
startTransition(async () => {
addOptimistic(!optimistic.liked);
await toggleLike(postId, !optimistic.liked);
})
}
>
{optimistic.liked ? "❤️" : "🤍"} {optimistic.likes}
</button>
);
}Why You Need a Transition
Optimistic updates must happen inside a transition. Calling addOptimistic outside one throws an error.
- Use
useTransitionfor button clicks, or - Use
<form action={...}>— form actions are wrapped in a transition automatically
The transition tells React the update is non-urgent and tied to a pending async action. When the action finishes (and revalidation arrives), the optimistic value is replaced by the real one.
Optimistic Comments with a Form
For adding a comment, the form's action already runs in a transition, so you can call addOptimistic directly inside it.
We append a temporary comment (with a sending flag) so the user sees it immediately, then submit to the server.
"use client";
import { useOptimistic, useRef } from "react";
import { addComment } from "./actions";
type Comment = { id: string; text: string; sending?: boolean };
export function Comments({ postId, comments }: {
postId: string; comments: Comment[];
}) {
const formRef = useRef<HTMLFormElement>(null);
const [optimistic, addOptimistic] = useOptimistic(
comments,
(state, text: string) => [
...state,
{ id: crypto.randomUUID(), text, sending: true },
]
);
return (
<>
<ul>
{optimistic.map((c) => (
<li key={c.id} style={{ opacity: c.sending ? 0.5 : 1 }}>
{c.text}
</li>
))}
</ul>
<form
ref={formRef}
action={async (formData) => {
const text = formData.get("text") as string;
addOptimistic(text);
formRef.current?.reset();
await addComment(postId, text);
}}
>
<input name="text" />
<button type="submit">Post</button>
</form>
</>
);
}The Mental Model: Snapshots, Not Mutations
useOptimistic does not change your real state. It layers a temporary view on top.
- The base state is your prop/state from the server.
- The optimistic value lives only while a transition is pending.
- When the transition settles, React throws the optimistic value away and re-renders from the (now updated) base state.
This is why rollback is free: there is nothing to undo, you simply stop showing the optimistic layer.
A Reusable Toggle Reducer
The update function is just a pure reducer. You can model any toggle — likes, bookmarks, follow/unfollow — by switching a boolean and deriving counts.
Because it is pure, you can unit-test it without React. Here is a standalone reducer that mirrors what we pass to useOptimistic.
type ToggleState = { active: boolean; count: number };
function toggleReducer(state: ToggleState, next: boolean): ToggleState {
return {
active: next,
count: state.count + (next ? 1 : -1),
};
}
const start: ToggleState = { active: false, count: 10 };
const afterLike = toggleReducer(start, true);
const afterUnlike = toggleReducer(afterLike, false);
console.log(afterLike); // { active: true, count: 11 }
console.log(afterUnlike); // { active: false, count: 10 }Handling Failures Gracefully
If the Server Action rejects, React reverts the optimistic state, but the user should know why. Wrap the action in try/catch and surface an error (a toast, an inline message).
The optimistic value disappears automatically; your job is only to communicate the failure.
startTransition(async () => {
addOptimistic(!optimistic.liked);
try {
await toggleLike(postId, !optimistic.liked);
} catch (err) {
// optimistic value already reverted by React
toast.error("Could not save your like. Try again.");
}
});Keep the Base State in Sync
For automatic reconciliation to work, the base state must come from the server and refresh after the mutation. Two common ways:
revalidatePath/revalidateTaginside the Server Action re-fetches the page data.- Passing fresh props down to the Client Component (e.g. from a Server Component parent).
If the base state never updates, the optimistic value will vanish and snap back to the old count — a classic flicker bug.
useOptimistic vs useState
You could fake optimism with useState, but you would have to manually save the previous value, write rollback logic, and clear it after the request. That is error-prone.
useOptimisticties the optimistic value to the transition lifecycle — auto-applied while pending, auto-discarded when settled.- It re-syncs whenever the base state changes, so server truth always wins.
Reach for useOptimistic whenever an action has a clear, predictable success outcome you can show immediately.
Quick Check
Test your understanding of how useOptimistic behaves.
Recap
You learned how to deliver instant feedback with useOptimistic in Next.js 15:
- useOptimistic(state, reducer) returns an optimistic view plus an
addOptimisticfunction. - Optimistic updates must run inside a transition (via
useTransitionor a formaction). - The real mutation lives in a Server Action that calls
revalidatePath/revalidateTagso the base state refreshes. - On success the optimistic layer is replaced by server truth; on failure React auto-reverts — you just surface an error.
- Prefer it over manual
useStaterollback for likes, comments, and toggles.
자주 묻는 질문
“useOptimistic으로 즉각적인 피드백 제공” 강의는 무료인가요?
네 — “useOptimistic으로 즉각적인 피드백 제공” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 Next.js 15 Fullstack (App Router + Server Actions) 강의 전체를 잠금 해제할 수 있습니다. Next.js 15 Fullstack (App Router + Server Actions) 강의에는 총 4개의 강의가 포함되어 있습니다.
“useOptimistic으로 즉각적인 피드백 제공”에서 뭘 배우나요?
액션이 백그라운드에서 처리되는 동안 좋아요, 댓글 및 토글에 낙관적 UI 업데이트를 적용하는 방법을 배웁니다. 브라우저에서 직접 실행하는 실습 코드로 Next.js 15 Fullstack (App Router + Server Actions)을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.
Next.js 15 Fullstack (App Router + Server Actions)을(를) 시작하는 데 경험이 필요한가요?
사전 경험은 필요하지 않습니다. CoddyKit의 Next.js 15 Fullstack (App Router + Server Actions)은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 4번째 강의입니다.
“useOptimistic으로 즉각적인 피드백 제공” 강의는 얼마나 걸리나요?
대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.
이 Next.js 15 Fullstack (App Router + Server Actions) 강의에서 코드를 작성하고 실행할 수 있나요?
네. 모든 Next.js 15 Fullstack (App Router + Server Actions) 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.
이 강의의 모든 강의
- form Action 속성을 활용한 점진적 향상
- useFormStatus를 활용한 대기 및 로딩 상태
- useActionState를 활용한 필드별 검증 오류
- useOptimistic으로 즉각적인 피드백 제공