CloudFlareにデプロイするためのRemixプロジェクト ページング用のリンクを用意（ページ数の表示）

## 概要

前回ページング情報受取用のクエリパラメーターを用意した。
これを一般的な利用を想定して、一覧内部に次のページや前のページにアクセスできるようなリンクを設置する。

## 前提

下記の内容が完了していること。

https://qiita.com/miriwo/items/5ba1a159f3368c6871a1

## 仕様

「ページング」と一言に言っても複数の内容が存在する。
まずはQiitaを参考に比較的シンプルなページング用リンクを設置してみようと思う。

仕様は下記を想定

- 「現在のページ/総ページ数」のようにページ情報を表示
- 前、もしくは次のページに表示するコンテンツがあれば、前、次のそれぞれのボタンを表示する

#### 最初のページ

![CleanShot 2024-07-05 at 09.54.26@2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306417/f5177e20-9bd4-3c32-9aaf-9d7704c2cd84.png)

#### 前、次両方のページがあるとき

![CleanShot 2024-07-05 at 09.52.11@2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306417/de25f938-16e7-18ae-fa23-358b78612407.png)

#### 最後のページ

![CleanShot 2024-07-05 at 09.55.59@2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306417/308d41cb-4411-6adf-b2d9-b0fd3c489759.png)

## 方法

### 総ページ数の取得

まずは総ページ数を計算して取得してみよう。

1. 下記のように`app/routes/tasks-loader.ts`を修正して画面に表示しようとしているタスクの総数を別途取得して返すように変更

    ```app/routes/tasks-loader.ts
    import type { LoaderFunctionArgs } from "@remix-run/cloudflare";
    
    export const loader = async ({ context, request }: LoaderFunctionArgs) => {
      const url = new URL(request.url);
      const searchParams = url.searchParams;
    
      const isCompleteParam: unknown = searchParams.get("isComplete");
      const isComplete: boolean = isCompleteParam === "true" ? true : false;
    
      const isDeletedParam: unknown = searchParams.get("isDeleted");
      const isDeleted: boolean = isDeletedParam === "true" ? true : false;
    
      const taskCategoryMasterIdParams: string[] = searchParams.getAll("taskCategoryMasterId");
      const taskCategoryMasterIds: number[] = taskCategoryMasterIdParams.map(id => Number(id));
    
      const page: number = Number(searchParams.get("page")) || 1; // 設定されていなかったらデフォルト値の1を設定
      const pageSize: number = 5; // 1ページあたりの表示数
    
      const isTrue = 1; // フラグONの意味 マジックナンバー申し訳ない。。

      interface whereCondition {
        is_complete?: number;
        deleted_at?: { not: null };
        task_category_master_id?: { in: number[] };
      }

      // whereの条件を定義
      const whereCondition = {
        ...(isComplete && { is_complete: isTrue }),
        ...(isDeleted && { deleted_at: { not: null } }),
        ...(taskCategoryMasterIds.length > 0 && { task_category_master_id: { in: taskCategoryMasterIds } }),
      };
    
      try {
        const tasks = await context.db.tasks.findMany({
          where: whereCondition as WhereCondition,
          include: {
            task_category_master: true, // schema.prismaで定義したリレーションを指定
          },
          skip: (page - 1) * pageSize, // スキップするレコード数
          take: pageSize, // 取得するレコード数
        });
        const tasksCount = await context.db.tasks.count({
          where: whereCondition as WhereCondition,
        });
        return {
          tasks,
          tasksCount,
        };
      } catch (error) {
        console.error("Failed to load tasks:", error);
        throw new Response("Internal Server Error", { status: 500 });
      }
    }
    ```

1. 下記を実行して`types.ts`ファイルを追加

    ```sh
    touch app/types.ts
    ```

1. 作成した`app/types.ts`を下記のように記載

    ```app/types.ts
    export interface TaskListInfos {
      tasks: Task[];
      tasksCount: number;
      pageSize: number;
      page: number;
    }
    
    export interface Task {
      id: string;
      name: string;
      memo: string;
      is_complete: boolean;
      task_category_master_id: string;
      created_at: string;
      updated_at: string;
      task_category_master: {
        id: string;
        name: string;
      };
    }
    ```

1. `app/routes/tasks.tsx`を下記の様に修正

    ```app/routes/tasks.tsx
    import type { MetaFunction } from "@remix-run/cloudflare";
    import type { TaskListInfos, Task } from "~/types";

    import { loader } from "./tasks-loader";
    export { loader };
    import { useLoaderData } from "@remix-run/react";
    
    export const meta: MetaFunction = () => {
      return [
        { title: "tasks" },
        {
          name: "description",
          content: "tasks",
        },
      ];
    };
    
    export default function Index() {
      const taskListInfos = useLoaderData<TaskListInfos>();
      const tasks = taskListInfos.tasks;
      const tasksCount = taskListInfos.tasksCount;
      const pageSize = taskListInfos.pageSize;
      const nowPage = taskListInfos.nowPage;
      const totalPage = Math.ceil(tasksCount / pageSize); // 切り上げで整数を返す
      return (
        <div style={{ fontFamily: "system-ui, sans-serif", lineHeight: "1.8" }}>
          <h1>tasks</h1>
          <ul>
            {tasks.map((task: Task) => (
              <li key={task.id}>
                <div>id: {task.id}</div>
                <div>name: {task.name}</div>
                <div>memo: {task.memo}</div>
                <div>is complete: {task.is_complete}</div>
                <div>task category: {task.task_category_master.name}</div>
                <div>created at: {task.created_at}</div>
                <div>updated at: {task.updated_at}</div>
              </li>
            ))}
          </ul>
          <div>
            <p>{nowPage} / {totalPage}</p>
          </div>
        </div>
      );
    }
    ```

1. `/tasks`にアクセスしてタスクの並びの下部に「現在のページ数 / 総ページ数」が表示されれば完了

    ![CleanShot 2024-07-05 at 10.24.46@2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306417/85e65191-95f0-e863-a82e-fd44565e8595.png)

## やったことの簡単なメモ

1. 一覧表示しようとしているタスクの総数を取得
1. タスク総数、1ページあたりのタスク数を使って総ページ数を計算
1. 現在のページ数をクエリパラメーターから取得
1. 現在のページ数と総ページ数を画面に表示