낙관적 업데이트 구현
tRPC와 클라이언트 측 캐시로 낙관적 업데이트를 구현하여 즉각적인 사용자 경험을 제공합니다.
낙관적 업데이트 구현은(는) CoddyKit의 무료 tRPC End-to-End Type Safe APIs 강의입니다. 이것은 4개 중 2번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 tRPC End-to-End Type Safe APIs 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. tRPC End-to-End Type Safe APIs 강의에는 총 4개의 강의가 포함되어 있습니다.
이 강의의 일부는 아직 번역되지 않았으며 영어로 표시됩니다.
Instant Feedback with Optimistic Updates
Imagine clicking 'Like' on a post. You expect it to show 'Liked' instantly, right? That's the magic of Optimistic Updates!
Optimistic updates make your app feel incredibly fast and responsive. They improve the user experience by giving immediate feedback.
What Are Optimistic Updates?
An optimistic update means updating the user interface (UI) before the server has confirmed the change. You 'optimistically' assume the server request will succeed.
- Instant UI update: The user sees their action immediately reflected.
- Background server call: The actual data change is sent to the backend.
- Rollback on error: If the server fails, the UI reverts to its previous state.
Why Use Them with tRPC?
tRPC, combined with a client-side caching library like React Query (or TanStack Query), provides powerful tools for managing server state and implementing optimistic updates.
React Query's useMutation hook is central to this. It allows you to define callbacks for when a mutation starts, succeeds, or fails, enabling precise control over the UI.
The `onMutate` Callback
When you trigger a mutation, React Query calls the onMutate function before sending the request to the server. This is where you perform the optimistic UI update.
Inside onMutate, you typically:
- Cancel any ongoing fetches for the data you're about to change.
- Snapshot the current data so you can roll back if needed.
- Update the cache with the new, optimistic data.
Setting Up `onMutate`
Let's look at a simplified example for 'liking' a post. We'll use queryClient.setQueryData to update the cache optimistically.
import { trpc } from '../utils/trpc';
import { useQueryClient } from '@tanstack/react-query';
function PostLikeButton({ postId }: { postId: string }) {
const queryClient = useQueryClient();
const likeMutation = trpc.post.like.useMutation({
onMutate: async (newLike) => {
// 1. Cancel any outgoing refetches
await queryClient.cancelQueries(['post', postId]);
// 2. Snapshot the previous value
const previousPost = queryClient.getQueryData(['post', postId]);
// 3. Optimistically update to the new value
queryClient.setQueryData(['post', postId], (old: any) => {
if (old) {
return { ...old, likes: old.likes + 1, isLiked: true };
}
return old;
});
return { previousPost }; // Context for onError
},
});
return (
<button onClick={() => likeMutation.mutate({ postId })}>Like</button>
);
}The `onError` Callback
If the server mutation fails, you need to revert the UI to its previous state. This is handled in the onError callback.
Here, you use the context object returned from onMutate (which holds your snapshot of previousPost) to restore the cache to its original value.
Implementing `onError` (Rollback)
Adding the onError handler to our useMutation setup:
import { trpc } from '../utils/trpc';
import { useQueryClient } from '@tanstack/react-query';
function PostLikeButton({ postId }: { postId: string }) {
const queryClient = useQueryClient();
const likeMutation = trpc.post.like.useMutation({
onMutate: async (newLike) => {
// ... (same as before)
const previousPost = queryClient.getQueryData(['post', postId]);
queryClient.setQueryData(['post', postId], (old: any) => {
if (old) return { ...old, likes: old.likes + 1, isLiked: true };
return old;
});
return { previousPost };
},
onError: (err, newLike, context) => {
// Rollback the cache to the previousPost
queryClient.setQueryData(['post', postId], context?.previousPost);
// Optionally show an error toast
console.error("Failed to like post: ", err.message);
},
});
return (
<button onClick={() => likeMutation.mutate({ postId })}>Like</button>
);
}The `onSuccess` Callback
Once the server successfully processes the mutation, the onSuccess callback is triggered. At this point, your optimistic UI update is correct, but you still want to ensure data consistency.
The best practice here is to invalidate relevant queries. This tells React Query to refetch the data in the background, ensuring your client-side cache is perfectly in sync with the server.
Completing the Optimistic Flow
Here's the full useMutation with onSuccess to invalidate and refetch data after a successful server response:
import { trpc } from '../utils/trpc';
import { useQueryClient } from '@tanstack/react-query';
function PostLikeButton({ postId }: { postId: string }) {
const queryClient = useQueryClient();
const likeMutation = trpc.post.like.useMutation({
onMutate: async (newLike) => {
await queryClient.cancelQueries(['post', postId]);
const previousPost = queryClient.getQueryData(['post', postId]);
queryClient.setQueryData(['post', postId], (old: any) => {
if (old) return { ...old, likes: old.likes + 1, isLiked: true };
return old;
});
return { previousPost };
},
onError: (err, newLike, context) => {
queryClient.setQueryData(['post', postId], context?.previousPost);
console.error("Failed to like post: ", err.message);
},
onSuccess: () => {
// Invalidate and refetch the post data to ensure consistency
queryClient.invalidateQueries(['post', postId]);
},
});
return (
<button onClick={() => likeMutation.mutate({ postId })}>Like</button>
);
}Benefits and Considerations
Optimistic updates are fantastic for user experience, but they add complexity:
- Pro: Instant UI feedback, perceived performance boost.
- Pro: Reduces loading spinners and waiting times.
- Con: Requires careful rollback logic for errors.
- Con: Can be complex for operations that depend on server-generated IDs or complex data transformations.
Use them thoughtfully, especially for actions where immediate feedback is critical and conflicts are rare.
Check Your Understanding
Consider an optimistic update for adding an item to a shopping cart. Which of the following actions are typically performed within the onMutate callback?
Recap: Optimistic Updates
In this lesson, we explored optimistic updates, a powerful technique to enhance user experience by providing instant feedback.
- We learned how
onMutateis used to optimistically update the UI and prepare for potential rollbacks. - We saw how
onErrorhandles reverting the UI to its previous state if the server request fails. - Finally, we covered how
onSuccessensures data consistency by invalidating and refetching data after a successful server response.
Mastering optimistic updates helps you build highly responsive and user-friendly tRPC applications!
자주 묻는 질문
“낙관적 업데이트 구현” 강의는 무료인가요?
네 — “낙관적 업데이트 구현” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 tRPC End-to-End Type Safe APIs 강의 전체를 잠금 해제할 수 있습니다. tRPC End-to-End Type Safe APIs 강의에는 총 4개의 강의가 포함되어 있습니다.
“낙관적 업데이트 구현”에서 뭘 배우나요?
tRPC와 클라이언트 측 캐시로 낙관적 업데이트를 구현하여 즉각적인 사용자 경험을 제공합니다. 브라우저에서 직접 실행하는 실습 코드로 tRPC End-to-End Type Safe APIs을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.
tRPC End-to-End Type Safe APIs을(를) 시작하는 데 경험이 필요한가요?
사전 경험은 필요하지 않습니다. CoddyKit의 tRPC End-to-End Type Safe APIs은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 2번째 강의입니다.
“낙관적 업데이트 구현” 강의는 얼마나 걸리나요?
대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.
이 tRPC End-to-End Type Safe APIs 강의에서 코드를 작성하고 실행할 수 있나요?
네. 모든 tRPC End-to-End Type Safe APIs 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.