0Pricing
tRPC End-to-End Type Safe APIs · レッスン

楽観的更新の実装

tRPCとクライアント側キャッシュで楽観的更新を実装し、即時性のあるユーザー体験を提供する方法を学びます。

「楽観的更新の実装」はCoddyKit上の無料tRPC End-to-End Type Safe APIsレッスンです。 これはレッスン2/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応の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 onMutate is used to optimistically update the UI and prepare for potential rollbacks.
  • We saw how onError handles reverting the UI to its previous state if the server request fails.
  • Finally, we covered how onSuccess ensures 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時間対応のAIチューター)、tRPC End-to-End Type Safe APIsコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 tRPC End-to-End Type Safe APIsコースには全4レッスンが含まれています。

「楽観的更新の実装」で何を学びますか?

tRPCとクライアント側キャッシュで楽観的更新を実装し、即時性のあるユーザー体験を提供する方法を学びます。 ブラウザで直接実行するハンズオンコードでtRPC End-to-End Type Safe APIsを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

tRPC End-to-End Type Safe APIsを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのtRPC End-to-End Type Safe APIsは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン2/4です。

「楽観的更新の実装」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このtRPC End-to-End Type Safe APIsレッスンでコードを書いて実行できますか?

はい。すべてのtRPC End-to-End Type Safe APIsレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. 効率的なリクエストのバッチ処理
  2. 楽観的更新の実装
  3. tRPCによるファイルアップロード
  4. 無限クエリとカーソルベースのページネーション
← tRPC End-to-End Type Safe APIsに戻る