【基本編】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アプリケーション実装を行うことができます。費用をかけてでも一定の安心を手に入れるための選択肢として、商用ライブラリの導入を検討してみるのはいかがでしょうか。

