0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

フロント自動更新hooks

Posted at
# 【フロントエンド自動更新編】登録・更新後に一覧を自動反映させる完全な方法

## はじめに:なぜ「リロードしないと反映されない」のか?
この問題は、フロントエンドが保持している「ユーザー一覧データ」が、サーバー側でデータが変更されたことを知らないために発生します。

-   **変更前**: フロントエンドは、最初に全件取得用の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}: CreateUserDrawerEditUserDrawerの両方に、同じ成功ハンドラを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' });
    }
  };
  // ...
}

まとめ:データの流れ

この**「成功イベントを上に伝え、親が再取得のスイッチを押す」**というパターンにより、データの流れは以下のようになります。

  1. 子(Drawer) -> 親: onSuccess()が呼ばれ、イベントが親に伝わります。
  2. : handleMutationSuccessが実行されます。
  3. 親 -> フック: 親がuseUsersフックから受け取っていたrefetch()関数を実行します。
  4. フック -> BFF -> Backend -> DB: 一覧取得のAPIコールが再度実行されます。
  5. DB -> ... -> フック: 最新のデータがフックに返ってきます。
  6. フック -> 親 -> 子: useUsersフックのusersデータが更新され、Reactが自動的に親コンポーネントと、usersを受け取っているUserList子コンポーネントを再描画します。

結果として、ユーザーは手動でリロードすることなく、常に最新の一覧を見ることができるのです。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?