対象読者
- Next.jsのServer ActionsとWijmoの連携方法を学びたい開発者
- 国産の商用UIライブラリに魅力を感じている開発者
前提環境
筆者の検証環境は以下の通りです。
- macOS Tahoe 16.0.1
- Node.js 25.2.0
- npm 11.6.2
- Next.js 15.4.10
- React 19.2.0
- Prisma 6.19.0
- MySQL 8.0
- Wijmo 5.20252.42
企業製UIライブラリという選択肢
オープンソースライブラリには、メンテナンス停止リスクや悪意のあるコードの混入リスク、依存関係の複雑さといった課題があります。一方、商用ライブラリは継続的なサポート、安定性、長期運用の観点で安心感があります。
「Wijmo」は、メシウス(旧グレープシティ)が開発するJavaScriptライブラリです。FlexGrid、Chart、Input系コントロールなど豊富なUIコンポーネントを提供し、React、Angular、Vue.jsなど主要フレームワークに対応しています。特にFlexGridはExcel風の操作感と大量データ対応、ソート・フィルター機能が強みです。日本語ドキュメント・サポートも充実しています。
本記事では、Next.js App RouterのServer ActionsとWijmoの連携方法、FlexGridを使った実践的なCRUDアプリケーションの実装、Server ComponentとClient Componentの使い分けを学びます。
本記事で作成するアプリケーション
本記事では、商品注文データを管理するCRUDアプリケーションを実装します。WijmoのInputNumberやInputDateコントロールを使った登録フォーム、FlexGridによるデータ一覧表示とインライン編集、ソート・フィルター機能、削除ボタンを備え、すべての機能がServer Actionsを通じてサーバーサイドと連携します(図1)。
Next.jsとServer Actions
Server Actionsは、APIエンドポイント不要でシンプルな実装が可能なNext.jsのデータ送信機能です。TypeScriptとの相性が良く型安全性が向上し、Server ComponentsやClient Componentsとの自然な連携ができます。従来のAPI Routesでは/api/xxxエンドポイントを定義してfetchで呼び出す必要がありましたが、Server Actionsでは関数を直接呼び出すだけで済みます。
'use server'ディレクティブをファイルの先頭に配置すると、ファイル内の全関数がServer Actionsになります(リスト1)。
"use server";
export async function createOrder(formData: FormData) {
// サーバーサイドで実行される処理
}
この例では、クライアントから直接createOrder関数を呼び出しつつ、実際に関数が実行されるのはサーバー側、という動きになります。
プロジェクトのセットアップ
まず、Next.jsプロジェクトを作成します。以下のコマンドを実行し、TypeScript、ESLint、Tailwind CSS、App Routerを有効にする推奨設定を選択します(リスト2)。
npx create-next-app@latest wijmo-serveractions-app cd wijmo-serveractions-app
プロジェクトが作成されたら、npm run devで開発サーバーを起動し、正常に動作することを確認します(図2)。
確認できたら次へ進みましょう。
MySQLとPrismaの設定
次に、データベース環境を構築します。今回はMySQLをPrismaから操作することにします。読者各位の慣れた方法でMySQLを起動して、PrismaでORMを設定しましょう(リスト3)。
# Prismaのインストール npm install prisma --save-dev npm install @prisma/client # Prisma初期化(MySQL用) npx prisma init --datasource-provider mysql
Prismaの初期化後、.envファイルに接続設定を記載します(リスト4)。
# 各位の環境に合わせたMySQLの接続先情報を書いてください DATABASE_URL="mysql://root:rootpassword@localhost:3306/wijmo_db"
次は、prisma/schema.prismaでサンプルデータ格納用のOrderDataモデルを定義します(リスト5)。
# 略
model OrderData {
id Int @id @default(autoincrement())
product String
price Int
quantity Int
orderdate DateTime
}
定義できたら、マイグレーションとシードを実行します。シード(seed)処理とは、データベースに初期データを投入する処理のことで、開発やテストに必要なサンプルデータを自動的に準備できます。今回のシード処理はprisma/seed.tsに定義済みです。詳細はダウンロードサンプルのprisma/seed.tsを参照してください(リスト6)。
npx prisma migrate dev --name init npx prisma db seed
これでデータベースにテーブルが作成され、サンプルデータが投入されます。npx prisma studioで投入されたデータを確認できます(図3)。
シード処理で入れたデータが入っていますね。
Wijmoのインストールとライセンス設定
最後に、Wijmoパッケージをインストールします。@mescius/wijmo.react.allで必要なコンポーネントが一括インストールされます(リスト7)。
npm install @mescius/wijmo.react.all
ライセンスキーは.envファイルにNEXT_PUBLIC_プレフィックスを付けて設定します。このプレフィックスにより、Client Componentからアクセス可能になります(リスト8)。
DATABASE_URL=... NEXT_PUBLIC_WIJMO_LICENSE_KEY="your-license-key"
ライセンスキーを設定したら、これを読み込んで適用するコンポーネントを作成します。見た目を持たないClient Componentとして定義し、setLicenseKey()でライセンスを有効化します(リスト9)。
"use client";
import { setLicenseKey } from "@mescius/wijmo";
const licenseKey = process.env.NEXT_PUBLIC_WIJMO_LICENSE_KEY;
if (licenseKey) {
setLicenseKey(licenseKey);
}
export default function WijmoLicense() {
return null;
}
このコンポーネントをレイアウトファイルからインポートし、全ページで読み込まれるようにします(リスト10)。
import WijmoLicense from "@/components/WijmoLicense";
export default function RootLayout({ children }) {
return (
<html>
<body>
<WijmoLicense />
{children}
</body>
</html>
);
}
これでセットアップは完了です。
【基本編】Input系コントロールでデータ登録
このセクションでは、WijmoのInputNumberやInputDateコントロールを使ってフォームを作成し、Server Actionsでデータを登録する方法を解説します。
Server Actionsの作成
Server Actionsは'use server'ディレクティブを付けたファイルに定義します。FormDataから値を取得し、バリデーション後にPrismaでデータを作成します。処理完了後はrevalidatePath('/')でキャッシュを更新し、ページを再描画します(リスト11)。
"use server";
import { revalidatePath } from "next/cache";
import prisma from "@/lib/prisma";
export async function createOrder(prevState: ActionState, formData: FormData) {
const product = formData.get("product") as string;
const price = parseInt(formData.get("price") as string, 10);
// ...バリデーションとDB処理
await prisma.orderData.create({ data: { product, price, quantity, orderdate } });
revalidatePath("/");
return { success: true, message: "データを登録しました" };
}
このServer Actionsは、フォームから送信されたデータを受け取り、Prismaを使ってデータベースに保存します。処理完了後はrevalidatePathでページのキャッシュを無効化し、最新データを表示します。
フォームコンポーネント(Wijmo Input系コントロール)
Wijmoのコントロールは標準のHTML inputではないため、useStateで値を保持し、submit時にFormDataに追加する必要があります。日本語カルチャをインポートすることで、数値や日付の表示が日本語対応になります(リスト12)。
"use client";
import { useActionState, useState } from "react";
import { createOrder, type ActionState } from "@/actions/orderActions";
import "@mescius/wijmo.cultures/wijmo.culture.ja";
import { InputNumber, InputDate } from "@mescius/wijmo.react.input";
// initialStateは省略
export default function OrderForm() {
// (1) useActionState: Server Actionsの状態管理(React 19)
const [state, formAction, isPending] = useActionState(createOrder, initialState);
// Wijmoコントロールの値をReactで管理
const [price, setPrice] = useState<number>(0);
const handleSubmit = (formData: FormData) => {
// Wijmoの値をFormDataに追加
formData.set("price", price.toString());
formAction(formData);
};
return (
<form action={handleSubmit}>
{/* Wijmo InputNumberコントロール */}
<InputNumber value={price} valueChanged={(s) => setPrice(s.value ?? 0)} />
{/* 送信中はボタンを無効化 */}
<button disabled={isPending}>{isPending ? "登録中..." : "登録"}</button>
</form>
);
}
(1)のuseActionStateはReact 19で導入された新しいフックで、Server Actionsの実行状態と結果を管理します。第1引数にServer Actions関数、第2引数に初期状態を渡すと、現在の状態(state)、アクション実行関数(formAction)、実行中フラグ(isPending)の3つを返します。これにより、フォーム送信中のローディング表示やエラーハンドリングが簡潔に実装できます。
上記のコードを実装すると、WijmoのInputNumberコントロールを使った入力フォームが表示されます。数値入力時には自動的に3桁区切りのフォーマットが適用され、ユーザーは直感的にデータを入力できます(図4)。
フォームに値を入力して登録ボタンをクリックすると、Server Actionsが実行され、データベースに保存されます。
【応用編】FlexGridでCRUD処理
このセクションでは、FlexGridを使ってデータの表示、ソート・フィルター、インライン編集、削除機能を実装します。
データの表示(READ)とソート・フィルター
Server Componentで直接Prismaを呼び出してデータを取得し、Client ComponentのFlexGridにpropsで渡します。FlexGridはデフォルトでカラムヘッダークリックによるソートが可能です。FlexGridFilterを子コンポーネントとして配置するだけでフィルター機能が有効化されます。formatプロパティで数値(n0)や日付(yyyy/MM/dd)のフォーマットを指定できます(リスト13)。
"use client";
import { FlexGrid, FlexGridColumn } from "@mescius/wijmo.react.grid";
import { FlexGridFilter } from "@mescius/wijmo.react.grid.filter";
export default function OrderGrid({ orders }: Props) {
return (
<FlexGrid itemsSource={orders}>
<FlexGridFilter />
<FlexGridColumn binding="id" header="ID" isReadOnly={true} />
<FlexGridColumn binding="product" header="商品名" />
<FlexGridColumn binding="price" header="価格" format="n0" />
{/* ...その他のカラム */}
</FlexGrid>
);
}
このコードにより、データがFlexGridに表示されます。カラムヘッダーをクリックするとソートが実行され、フィルターアイコンから条件を指定してデータを絞り込むことができます(図5)。
図のように、価格列のフィルターパネルを開くと、値による絞り込みや条件指定が可能です。日本語カルチャが適用されているため、UIも日本語で表示されます。
データの更新(UPDATE)
FlexGridのcellEditEndedイベントで編集完了を検知し、Server Actionsを呼び出します。useTransitionはReact 19の機能で、非同期処理中の状態管理を行います。編集されたセルの情報(行、列、新しい値)を取得してServer Actionsに渡し、revalidatePath('/')でキャッシュを更新して最新データを再取得します(リスト14)。
const handleCellEditEnded = (grid: wjGrid.FlexGrid, e: wjGrid.CellRangeEventArgs) => {
const item = grid.rows[e.row].dataItem;
const binding = grid.columns[e.col].binding;
const newValue = grid.getCellData(e.row, e.col, false);
startTransition(async () => {
await updateOrder(item.id, { [binding]: newValue });
});
};
handleCellEditEndedは、セル編集が終了した時に呼び出されるイベントハンドラです。引数としてグリッドインスタンス(grid)とイベント引数(e)を受け取るので、これらの値を元に編集したセル、入力値を取り出します。それぞれの行の意味は、以下の通りです。
+
grid.rows[e.row].dataItem:編集された行のデータオブジェクト+
grid.columns[e.col].binding:編集されたカラムのプロパティ名("product"、"price"など)+
grid.getCellData(e.row, e.col, false):編集後の新しい値を取得
getCellDataメソッドの第3引数をfalseにすることで表示用フォーマットではなく生の値を取得できます。あとは、これらの情報をもとにServerAction(updateOrder関数)を呼び出し、データベースを更新するだけです。
これでユーザーはセルを編集したところで、データが即座に反映される——Excelのような操作感を体感できます(図6)。
データの削除(DELETE)
FlexGridでデータを削除するための手順として、まずカスタムHTML(削除ボタン)をグリッド内に挿入します。React環境において、WjGridのcellTemplateプロパティはJSXを直接受け取れないため、formatItemイベントを利用してHTMLを直接挿入し、イベントデリゲーションを用いてボタンのクリックイベントを処理します(リスト15)。
const handleFormatItem = (grid: wjGrid.FlexGrid, e: wjGrid.FormatItemEventArgs) => {
if (e.panel === grid.cells && grid.columns[e.col].header === "操作") {
const item = grid.rows[e.row].dataItem;
e.cell.innerHTML = `<button class="delete-btn" data-id="${item.id}">削除</button>`;
}
};
handleFormatItem関数は、FlexGridが各データセルを描画する直前に発生するformatItemイベントを処理するためのハンドラです。このイベントを利用することで、セルの内容をカスタマイズできます。
まず、このハンドラは処理対象のセルを絞り込んでいます。具体的には「e.panel === grid.cells」という条件で通常のデータ本体のセルであることを確認し、さらに「grid.columns[e.col].header === "操作"」という条件で、列ヘッダーが「操作」となっている特定の列のみを対象としています。これにより、削除ボタンはデータ行の「操作」列に限定して表示されます。
対象のセルを特定したら、「const item = grid.rows[e.row].dataItem」で、描画中の行に対応するデータオブジェクト(レコード)を取得します。このデータオブジェクトから、レコードを一意に識別するためのID(item.id)を取り出します。
最後に、e.cell.innerHTMLに対して、削除ボタンのHTMLコードを直接挿入することでセルの内容を書き換えます。挿入されるHTMLには「<button class="delete-btn" data-id="${item.id}">削除</button>」という形で、ボタンのカスタムデータ属性であるdata-idに、先ほど取得したレコードの一意なIDが埋め込まれています。これで、後でクリック時にどの行を削除するか判別できます。
サンプルを実行してみましょう。上の図のように、操作列に削除ボタンが配置され、ボタンクリックで確認ダイアログが表示されること、さらに[はい]ボタンをクリックすることでデータが削除されることが確認できます。
動作確認
完成したアプリケーションでは、ページ読み込み時にデータ一覧を表示(READ)、フォームからデータ登録(CREATE)、FlexGridセルをダブルクリックして編集(UPDATE)、削除ボタンでデータ削除(DELETE)が可能です。すべての操作がServer Actionsを通じてサーバーサイドと連携し、データベースと同期されます。
まとめ
Wijmoは、最新パラダイムのServer Actionsと組み合わせることで、より直感的なWebアプリケーション実装を行うことができます。費用をかけてでも一定の安心を手に入れるための選択肢として、商用ライブラリの導入を検討してみるのはいかがでしょうか。

