SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

メシウスの高速・軽量なJavaScript UIライブラリ「Wijmo」活用(AD)

Server Actionsと「Wijmo」でCRUD処理可能なデータグリッドを手軽に実装しよう

【基本編】Input系コントロールでデータ登録

 このセクションでは、WijmoのInputNumberやInputDateコントロールを使ってフォームを作成し、Server Actionsでデータを登録する方法を解説します。

Server Actionsの作成

 Server Actionsは'use server'ディレクティブを付けたファイルに定義します。FormDataから値を取得し、バリデーション後にPrismaでデータを作成します。処理完了後はrevalidatePath('/')でキャッシュを更新し、ページを再描画します(リスト11)。

[リスト11]actions/orderActions.ts(createOrder)
"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)。

[リスト12]components/OrderForm.tsx
"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)。

図4:フォーム登録画面
図4:フォーム登録画面

 フォームに値を入力して登録ボタンをクリックすると、Server Actionsが実行され、データベースに保存されます。

【応用編】FlexGridでCRUD処理

 このセクションでは、FlexGridを使ってデータの表示、ソート・フィルター、インライン編集、削除機能を実装します。

データの表示(READ)とソート・フィルター

 Server Componentで直接Prismaを呼び出してデータを取得し、Client ComponentのFlexGridにpropsで渡します。FlexGridはデフォルトでカラムヘッダークリックによるソートが可能です。FlexGridFilterを子コンポーネントとして配置するだけでフィルター機能が有効化されます。formatプロパティで数値(n0)や日付(yyyy/MM/dd)のフォーマットを指定できます(リスト13)。

[リスト13]components/OrderGrid.tsx(FlexGrid基本構造)
"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)。

図5:FlexGridのソート・フィルター機能
図5:FlexGridのソート・フィルター機能

 図のように、価格列のフィルターパネルを開くと、値による絞り込みや条件指定が可能です。日本語カルチャが適用されているため、UIも日本語で表示されます。

データの更新(UPDATE)

 FlexGridのcellEditEndedイベントで編集完了を検知し、Server Actionsを呼び出します。useTransitionはReact 19の機能で、非同期処理中の状態管理を行います。編集されたセルの情報(行、列、新しい値)を取得してServer Actionsに渡し、revalidatePath('/')でキャッシュを更新して最新データを再取得します(リスト14)。

[リスト14]components/OrderGrid.tsx(cellEditEndedハンドラ)
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)。

図6:インライン編集中の画面
図6:インライン編集中の画面

データの削除(DELETE)

 FlexGridでデータを削除するための手順として、まずカスタムHTML(削除ボタン)をグリッド内に挿入します。React環境において、WjGridのcellTemplateプロパティはJSXを直接受け取れないため、formatItemイベントを利用してHTMLを直接挿入し、イベントデリゲーションを用いてボタンのクリックイベントを処理します(リスト15)。

[リスト15]components/OrderGrid.tsx(formatItemハンドラ)
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が埋め込まれています。これで、後でクリック時にどの行を削除するか判別できます。

図7:削除ボタンの表示
図7:削除ボタンの表示

 サンプルを実行してみましょう。上の図のように、操作列に削除ボタンが配置され、ボタンクリックで確認ダイアログが表示されること、さらに[はい]ボタンをクリックすることでデータが削除されることが確認できます。

動作確認

 完成したアプリケーションでは、ページ読み込み時にデータ一覧を表示(READ)、フォームからデータ登録(CREATE)、FlexGridセルをダブルクリックして編集(UPDATE)、削除ボタンでデータ削除(DELETE)が可能です。すべての操作がServer Actionsを通じてサーバーサイドと連携し、データベースと同期されます。

まとめ

 Wijmoは、最新パラダイムのServer Actionsと組み合わせることで、より直感的なWebアプリケーション実装を行うことができます。費用をかけてでも一定の安心を手に入れるための選択肢として、商用ライブラリの導入を検討してみるのはいかがでしょうか。

この記事は参考になりましたか?

この記事の著者

WINGSプロジェクト 中川幸哉(ナカガワユキヤ)

WINGSプロジェクトについて>有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表 山田祥寛)。主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手がける。2018年11月時点での登録メンバは55名で、現在も執筆メンバを募集中。興味のある方は、どしどし応募頂きたい。著書記事多数。 RSS X: @WingsPro_info(公式)、@WingsPro_info/wings(メンバーリスト) Facebook

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

山田 祥寛(ヤマダ ヨシヒロ)

静岡県榛原町生まれ。一橋大学経済学部卒業後、NECにてシステム企画業務に携わるが、2003年4月に念願かなってフリーライターに転身。Microsoft MVP for Visual Studio and Development Technologies。執筆コミュニティ「WINGSプロジェクト」代表。主な著書に「独習シリーズ(Java・C#・Python・PHP・Ruby・JSP&サーブレットなど)」「速習シリーズ(ASP.NET Core・Vue.js・React・TypeScript・ECMAScript、Laravelなど)」「改訂3版JavaScript本格入門」「これからはじめるReact実践入門」「はじめてのAndroidアプリ開発 Kotlin編 」他、著書多数

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

提供:メシウス株式会社

【AD】本記事の内容は記事掲載開始時点のものです 企画・制作 株式会社翔泳社

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/22788 2026/01/19 12:00

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング