# 【フロントエンド自動更新編】登録・更新後に一覧を自動反映させる完全な方法
## はじめに:なぜ「リロードしないと反映されない」のか?
この問題は、フロントエンドが保持している「ユーザー一覧データ」が、サーバー側でデータが変更されたことを知らないために発生します。
- **変更前**: フロントエンドは、最初に全件取得用のAPI(`useUsers`フック)を呼び出し、その結果を画面に表示しています。
- **変更時**: 「新規登録」や「更新」のAPI(`useCreateUser`フックなど)を呼び出します。サーバーのデータは正しく更新されます。
- **変更後**: しかし、フロントエンドは、最初に取得した古い一覧データを表示し続けています。サーバーのデータが変わったことを、誰も教えてくれないからです。
この問題を解決するのが、**「書き込み(Mutation)の成功をトリガーに、読み取り(Query)を再実行させる」**というテクニックです。URQLでは、`useQuery`フックが返す`refetch`関数を使うことで、これを非常にエレガントに実現できます。
## Step 1: データ取得フックの準備 - 「再取得スイッチ」を手に入れる
まず、一覧データを取得する既存のカスタムフックに、データを再取得するための「スイッチ」である`refetch`関数を追加します。URQLの`useQuery`は、この関数を標準で返してくれます。
````tsx
// 📍 レイヤー: Frontend (カスタムフック)
// 📂 ファイル: apps/frontend/src/hooks/useUsers.ts (全件取得フックの修正版)
import { useQuery } from 'urql';
import { GetUsersQuery, GetUsersQueryVariables, GetUsersDocument } from '@/graphql/generated';
export const useUsers = () => {
// 1. `useQuery`の戻り値から、結果(`result`)だけでなく、
// クエリを再実行するための関数(`executeQuery` - refetchのエイリアス)も受け取ります。
const [result, refetch] = useQuery<GetUsersQuery, GetUsersQueryVariables>({
query: GetUsersDocument,
});
// 2. UIコンポーネントが使いやすいように、結果と「再取得スイッチ」を返します。
return {
users: result.data?.users,
loading: result.fetching,
error: result.error,
// ★★★ここが最重要ポイント★★★
// `refetch`関数を、このフックの戻り値に含めます。
// これにより、他のコンポーネントからこの「スイッチ」を押せるようになります。
refetch,
};
};
ここでの解説:
-
const [result, refetch] = useQuery(...)
:useQuery
は、1番目の要素にクエリ結果を、2番目の要素にそのクエリを再実行するための関数を返します。このrefetch
関数こそが、今回のキーパーソンです。
Step 2: 親コンポーネントの実装 - 全てのロジックを繋ぐ「司令塔」
次に、全ての状態とイベントハンドラを管理する親コンポーネントを実装します。このコンポーネントが、**「更新が成功したら、一覧を再取得する」**という連携処理の責任を持ちます。
apps/frontend/src/app/users/page.tsx
// 📍 レイヤー: Frontend (ページコンポーネント)
// 📂 ファイル: apps/frontend/src/app/users/page.tsx (例)
'use client';
import { useState } from 'react';
import { Box, Heading, Button } from '@chakra-ui/react';
// --- 1. 必要なフックとコンポーネントを全てインポート ---
import { useUsers } from '@/hooks/useUsers'; // ★`refetch`を含む全件取得フック
import { CreateUserDrawer } from '@/components/CreateUserDrawer'; // 新規登録用Drawer
import { EditUserDrawer } from '@/components/EditUserDrawer'; // 更新用Drawer
import { UserList } from '@/components/UserList'; // 一覧表示用コンポーネント
import type { User } from '@/graphql/generated';
export default function UserManagementPage() {
// --- 2. 状態とデータ取得の準備 ---
// 全件取得フックを呼び出し、データと「再取得スイッチ」の両方を受け取る
const { users, loading, error, refetch } = useUsers();
// 編集対象のユーザーを管理するstate
const [userToEdit, setUserToEdit] = useState<User | null>(null);
// 新規登録Drawerの開閉を管理するstate
const [isCreateDrawerOpen, setIsCreateDrawerOpen] = useState(false);
// --- 3. ★★★ここが連携の核心★★★ ---
// 子コンポーネント(Drawer)から成功が通知されたときに実行するイベントハンドラ
const handleMutationSuccess = () => {
console.log('登録または更新が成功しました。一覧を再取得します!');
// 1. `useUsers`フックから受け取った`refetch`関数を実行します。
// これにより、URQLは`GetUsers`クエリをBFFに再送信します。
refetch();
// 2. 開いている可能性のあるDrawerを全て閉じます。
setIsCreateDrawerOpen(false);
setUserToEdit(null);
};
// --- 4. Drawerを開くためのイベントハンドラ ---
const handleOpenCreateDrawer = () => setIsCreateDrawerOpen(true);
const handleOpenEditDrawer = (user: User) => setUserToEdit(user);
const handleCloseDrawers = () => {
setIsCreateDrawerOpen(false);
setUserToEdit(null);
};
return (
<Box p={5}>
<Heading size="lg" mb={4}>社員管理</Heading>
{/* 新規登録ボタン(Drawerを開くトリガー) */}
<Button onClick={handleOpenCreateDrawer} colorScheme="blue" mb={4}>
新規登録
</Button>
{/* --- 5. 子コンポーネントへのデータと「関数」の受け渡し --- */}
<UserList
users={users || []}
isLoading={loading}
error={error}
onEdit={handleOpenEditDrawer}
onDelete={/* ...削除処理... */}
/>
<CreateUserDrawer
isOpen={isCreateDrawerOpen}
onClose={handleCloseDrawers}
onSuccess={handleMutationSuccess} // ★成功通知用の関数を渡す
/>
<EditUserDrawer
isOpen={!!userToEdit}
userToEdit={userToEdit}
onClose={handleCloseDrawers}
onSuccess={handleMutationSuccess} // ★成功通知用の関数を渡す
/>
</Box>
);
}
ここでの解説:
-
handleMutationSuccess
関数: これが今回の主役です。この関数は、新規登録と更新の両方の成功イベントを処理します。中身はシンプルで、refetch()
を呼び出して一覧を再取得し、Drawerを閉じるだけです。 -
onSuccess={handleMutationSuccess}
:CreateUserDrawer
とEditUserDrawer
の両方に、同じ成功ハンドラをonSuccess
というpropsとして渡しています。これにより、どちらの操作が成功しても、同じ後処理(一覧の再取得)が実行されることが保証されます。
Step 3: 子コンポーネント(Drawer)の実装 - 親への成功通知
最後に、新規登録と更新のDrawerコンポーネントが、それぞれの処理に成功した際に、親から渡されたonSuccess
関数を呼び出すようにします。
CreateUserDrawer.tsx の handleSubmit
apps/frontend/src/components/createuserdrawer.tsx
// 📍 レイヤー: Frontend
// 📂 ファイル: apps/frontend/src/components/CreateUserDrawer.tsx
// ...
export const CreateUserDrawer = ({ onSuccess, onClose, ...props }) => {
const { createUser, creating } = useCreateUser();
const toast = useToast();
const handleSubmit = async (formData) => {
try {
await createUser(formData);
toast({ title: 'ユーザーを登録しました。', status: 'success' });
// ★★★ここで親に成功を通知する★★★
onSuccess();
} catch (error) {
toast({ title: '登録に失敗しました。', status: 'error' });
}
};
// ...
}
EditUserDrawer.tsx の handleSubmit
apps/frontend/src/components/edituserdrawer.tsx
// 📍 レイヤー: Frontend
// 📂 ファイル: apps/frontend/src/components/EditUserDrawer.tsx
// ...
export const EditUserDrawer = ({ onSuccess, onClose, ...props }) => {
const { updateUser, updating } = useUpdateUser();
const toast = useToast();
const handleSubmit = async (payload) => {
try {
await updateUser(payload);
toast({ title: 'ユーザー情報を更新しました。', status: 'success' });
// ★★★ここで親に成功を通知する★★★
onSuccess();
} catch (error) {
toast({ title: '更新に失敗しました。', status: 'error' });
}
};
// ...
}
まとめ:データの流れ
この**「成功イベントを上に伝え、親が再取得のスイッチを押す」**というパターンにより、データの流れは以下のようになります。
-
子(Drawer) -> 親:
onSuccess()
が呼ばれ、イベントが親に伝わります。 -
親:
handleMutationSuccess
が実行されます。 -
親 -> フック: 親が
useUsers
フックから受け取っていたrefetch()
関数を実行します。 - フック -> BFF -> Backend -> DB: 一覧取得のAPIコールが再度実行されます。
- DB -> ... -> フック: 最新のデータがフックに返ってきます。
-
フック -> 親 -> 子:
useUsers
フックのusers
データが更新され、Reactが自動的に親コンポーネントと、users
を受け取っているUserList
子コンポーネントを再描画します。
結果として、ユーザーは手動でリロードすることなく、常に最新の一覧を見ることができるのです。