2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Remix + Prisma でページング処理を実装してみた

Posted at

はじめに

今回は、RemixPrisma を使って簡単なページング処理を実装してみました。UI には Tailwind CSS ベースのコンポーネントライブラリである Flowbite React を使用しています。すべてのデータを一度に取得してフロントエンドでフィルターするのではなく、ページが切り替わるたびにデータベースから必要なデータを取得するようにしています。

なお、今回実装した内容は以下のリポジトリに置いてありますので、必要に応じて確認してみてください。

環境

  • Remix: 2.11.0
  • Tailwind CSS: 3.4.4
  • Flowbite React: 0.10.1
  • Prisma: 5.17.0

プロジェクトをセットアップ

Remix + Tailwind CSS

% npx create-remix@latest

この状態でローカルサーバーを起動すると Welcome to Remix が表示される状態となります。

% npm run dev

また、Tailwind CSS も Vite の PostCSS を使ってセットアップ済みとなっていました。手動でセットアップする場合は、こちらの公式ドキュメントが参考になります。

% tree
.
├── app
├── └── tailwind.css
├── postcss.config.js
├── tailwind.config.ts
...

Flowbite React

Flowbite React パッケージをインストールします。

% npm i flowbite-react

tailwind.config.tscontentplugin に Flowbite React の設定を追加します。

import flowbite from "flowbite-react/tailwind";  // 追加
import type { Config } from "tailwindcss";

export default {
  content: [
    "./app/**/{**,.client,.server}/**/*.{js,jsx,ts,tsx}",
    flowbite.content(),  // 追加
  ],
  theme: {
    extend: {},
  },
  plugins: [
      flowbite.plugin()  // 追加
  ],
} satisfies Config;

Prisma

Prisma パッケージをインストールと Prisma schema 関連ファイルを生成します。

% npm install prisma --save-dev
...
% npx prisma init

実装

各種パッケージの利用準備が整ったので、実装していきます。

UI

UI は Tailwind CSS ベースの Flowbite React を利用してみました。いちからページングの UI を実装するのに比べて提供されているコンポーネントを利用するほうが簡単に実装することができます。今回はページング処理を実装したいので、ページング処理部分に Pagination コンポーネント、データ表示部分に Table コンポーネントを利用して、サクッと UI を作成しました。

Monosnap Image 2024-08-03 17-50-30.png

Pagination コンポーネントは、必要なプロパティを渡してあげると、アクティブなページの表示や前のページ、次のページの制御、移動可能なページの表示などページング処理に必要な UI 側の制御をやってくれて、とても楽に組み込むことができました。

データ取得のタイミング

Remix でデータの取得処理は、Loader を使うことができるので、Loader でデータを取得するようにします。ただ、初期データだけなら、Loader でデータを取得するだけでよいのですが、今回はページ番号によってデータを取得する必要があるので、シンプルにクエリパラメータに page パラメータを追加することにしました。こうすることで URL でページ番号を指定できるようになります。直接特定のページを表示したい場合やブラウザバックなどもいい感じにしてくれて、良さそうですね。

export const loader = async ({ request }: LoaderFunctionArgs) => {
  const url = new URL(request.url);
  const pageNumber = parseInt(url.searchParams.get("page") ?? "1");
  const pageSize = 10;

  const { totalCount, items } = await fetchPagedItems(pageNumber, pageSize);
  const totalPages = Math.ceil(totalCount / pageSize);

  return json({ totalPages, pageNumber, items });
};

データ取得処理

ページを切り替えた際に、対象のデータのみ取得するように Prisma ORM を使って実装しています。実装のサンプルは以下のようにしています。

const fetchPagedItems = async (pageNumber: number, pageSize: number) => {
  const prisma = new PrismaClient();
  const totalCount = await prisma.item.count();
  const items = await prisma.item.findMany({
    skip: (pageNumber - 1) * pageSize,
    take: pageSize,
    orderBy: {
      id: "asc",
    },
  });

  return { totalCount, items };
};

findManyskiptake を使って、オフセットベースのページネーションで実装しています。

image.png
(※ Prisma/docs より)

Offset pagination は、どのページにもジャンプすることができ、任意の項目でソートすることができる点がメリットとなります。ただし、表示対象のデータが大量の場合の利用には向いていないようです。
公式ページで紹介されていますが、Cursor-based pagination のように Cursor を使ったページネーションもありますので、ユースケースに応じて、実装方法を選択するのが良さそうですね。

動作確認

選択したページに応じたデータが取得・表示されていることを確認できました。前後にページが存在する場合は、表示中のページ番号が真ん中に配置されるようになっているようです。ここらへんの制御をコンポーネント (Pagination) がよしなにやってくれるのもいいですね。

image.png

今回のように、ページング処理の際に Remix の useNavigate を使ってページを遷移させた際は、内部的には Fetch リクエストが利用されていることを確認できました。

image.png

参考

2
1
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
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?