useOptimistic に拡張性をもたせよう[カスタムフック: useOptimisticCount]
はじめに
ユーザーのフォロー・アンフォロー、投稿のお気に入り追加・削除などを行う際に 楽観的更新(optimistic updates) を用いることでUXを向上させます。
本記事では、react が提供している useOptimistic
を用いて、カウントやアクティブ状態の管理に焦点を当てたカスタムフックuseOptimisticCount
の作成過程とその機能について紹介します。
楽観的更新について
まず初めに、楽観的更新について説明します。
楽観的更新とは、アプリケーションのユーザーエクスペリエンス(UX)を高めるための技術的なアプローチです。これは、ユーザーにデータの更新が即座に行われたかのような感覚を提供する方法で、実際のデータの変更が確認される前にUIを更新します。
例えば、X のいいね機能に楽観的更新が使われています。X では、いいねのアイコンをタップした直後に色がつきますが、実はサーバへのリクエスト送信と UI 上のいいねの色の変化が同時に発生しています。つまり、サーバからの応答を待たずに UI を更新しています。
このようにして、ユーザーの操作を中断することなくスムーズな体験を続けられるので、全体的なUXが向上します。
ただし、楽観的更新は、操作が成功すると前提としてUIを先に更新するので、実際には失敗する場合も考慮して、適切なエラーハンドリングを行う必要があります。更新の失敗が頻発する場合、楽観的更新を用いることでユーザーの混乱や不信感を生じさせる可能性があるので、注意深く実装する必要があります。
カスタムフック:useOptimisticCount の実装方法
useOptimisticCountは、楽観的更新を活用したカスタムフックです。このセクションでは、その具体的な実装方法について説明します。
まず、フックの全体的なコードを以下に示します。
import { experimental_useOptimistic as useOptimistic } from "react";
import { useToast } from "@/src/components/ui/use-toast";
import { ActionsResult } from "../types/ActionsResult";
type Props = {
count: number;
isActive: boolean;
activeAction: (id: string) => Promise<ActionsResult>;
inactiveAction: (id: string) => Promise<ActionsResult>;
};
export const useOptimisticCount = ({ count, isActive, activeAction, inactiveAction }: Props) => {
const [optimisticState, setOptimisticState] = useOptimistic({ count, isActive });
const { toast } = useToast();
const updateCount = async (id: string) => {
const action = isActive ? inactiveAction : activeAction;
const newState = {
count: optimisticState.count + (isActive ? -1 : 1),
isActive: !optimisticState.isActive,
};
setOptimisticState(newState);
try {
const result = await action(id);
if (!result.isSuccess) {
toast({
variant: "destructive",
title: result.error,
});
setOptimisticState(optimisticState);
}
} catch (error) {
console.error(error);
setOptimisticState(optimisticState);
toast({
variant: "destructive",
title: "エラーが発生しました🥲",
});
}
};
return {
optimisticState,
updateCount,
};
};
Props の定義
type Props = {
count: number;
isActive: boolean;
activeAction: (id: string) => Promise<ActionsResult>;
inactiveAction: (id: string) => Promise<ActionsResult>;
};
上記のPropsで、このフックが受け取るパラメータを定義しています。それぞれのパラメータの説明は以下の通りです。
パラメータ | 説明 |
---|---|
count | これは現在のカウント値を示すもので、例えば「いいね」の数などの数値を管理する際に使用します。 |
isActive | このフラグは、何らかのトグル機能(例:フォロー中かどうか)の現在の状態を示します。 |
activeAction inactiveAction | これらはアクティブ状態と非アクティブ状態を切り替える際に、サーバーサイドで実行される関数を指定するためのものです。具体的には、例えばユーザーをフォローする、またはフォローを解除するようなアクションをサーバーに伝える関数をここに設定します。 |
ロールバック処理も対応
エラーハンドリングも考慮しており、何か問題が発生した場合には楽観的更新を元に戻す(ロールバックする)対応も含めています。
try {
const result = await action(id);
if (!result.isSuccess) {
toast({
variant: "destructive",
title: result.error,
});
+ setOptimisticState(optimisticState);
}
} catch (error) {
console.error(error);
+ setOptimisticState(optimisticState);
toast({
variant: "destructive",
title: "エラーが発生しました🥲",
});
}
カスタムフック:useOptimisticCount の利用方法
あとは利用方法についてですが、Server Component で DB からデータを取得し、count と isActive に該当するデータやフラグを Client Component に渡します。
また activeAction と inactiveAction に関しては、Server Actions で定義されたサーバーサイドで動作する関数を指定します。
以下はユーザーのフォローに関する useOptimisticCount の呼び出し部分です。
const { optimisticState, updateCount } = useOptimisticToggle({
count: followersCount,
isActive,
activeAction: followUser,
inactiveAction: unFollowUser,
});
以下は実装例ですが、ボタンを押下すると、カウントが瞬時に更新されることが確認できます。
まとめ
useOptimisticCount は、ユーザーのフォローやアンフォロー、投稿のお気に入り追加・削除といったアクションに対するフィードバックを即座に提供することを目的としたカスタムフックです。このフックを使用することで、サーバーからの確定的なレスポンスを待たずに、一時的なUIの更新を行うことができます。結果として、ユーザーはアプリケーションの反応速度の向上を感じ、全体的な UX が向上します。また、楽観的更新を行う際には、エラーハンドリングの実装も重要であり、useOptimisticCount はその両方の側面を考慮して設計しています。
ぜひ、useOptimisticCount を試してみて、その効果を実感していただけると嬉しいです🙌
以上です!
Discussion