ReoGrid ReoGrid Web

グリッドをReactのstateと同期

ReoGrid Web は命令的なキャンバスコンポーネントです。仮想DOMのリストのように、プロップが変わるたびに再レンダリングはしません。React と統合する正しいパターンは 一方向の購読 です。グリッドが自身のデータを所有し、React は変更を購読します。

完成例

import { useEffect, useRef, useState } from 'react';
import { Reogrid, type ReogridInstance } from '@reogrid/lite/react';

type Row = { product: string; price: number; qty: number };

const initialData: Row[] = [
  { product: 'Widget', price:  9.99, qty: 40 },
  { product: 'Gadget', price: 24.50, qty: 12 },
  { product: 'Gizmo',  price: 39.00, qty:  7 },
];

export default function App() {
  const gridRef = useRef<ReogridInstance>(null);
  const [rows, setRows] = useState<Row[]>(initialData);

  function onReady({ worksheet }: ReogridInstance) {
    // 1. Initial load
    ['Product', 'Price', 'Qty'].forEach((h, c) =>
      worksheet.cell(0, c).setValue(h).setStyle({ bold: true }),
    );
    initialData.forEach((row, i) => {
      worksheet.cell(i + 1, 0).setValue(row.product);
      worksheet.cell(i + 1, 1).setValue(String(row.price));
      worksheet.cell(i + 1, 2).setValue(String(row.qty));
    });

    // 2. Subscribe to changes
    worksheet.on('cellValueChange', ({ row, column, newValue }) => {
      if (row === 0) return;  // header row
      const idx = row - 1;
      setRows((prev) => {
        const next = [...prev];
        if (!next[idx]) return prev;
        const updated = { ...next[idx] };
        if (column === 0) updated.product = newValue;
        if (column === 1) updated.price   = Number(newValue) || 0;
        if (column === 2) updated.qty     = Number(newValue) || 0;
        next[idx] = updated;
        return next;
      });
    });
  }

  const total = rows.reduce((sum, r) => sum + r.price * r.qty, 0);

  return (
    <div>
      <div style={{ padding: 12, fontSize: 18 }}>
        Total: <strong>${total.toFixed(2)}</strong>
      </div>
      <Reogrid ref={gridRef} onReady={onReady} style={{ height: 400 }} />
    </div>
  );
}

ヘッダーの total は、セルを編集するたびに再レンダリングされます。

外部の更新をグリッドへ書き戻す

グリッドの外側(WebSocket、親コンポーネントなど)でデータが変わり、それを反映する必要があるとき:

useEffect(() => {
  const ws = gridRef.current?.worksheet;
  if (!ws) return;
  externalData.forEach((row, i) => {
    ws.cell(i + 1, 0).setValue(row.product);
    ws.cell(i + 1, 1).setValue(String(row.price));
    ws.cell(i + 1, 2).setValue(String(row.qty));
  });
}, [externalData]);

すでにグリッドから同期している React state でこれを駆動してはいけません。無限ループになります。書き戻すのは、グリッドの外部にあるソースからのみにしてください。

よくある落とし穴

  1. レンダー中にグリッドのメソッドを呼ばない。 gridRef.currentuseEffect が走るまで null です。セットアップは onReady か effect の中で行ってください。
  2. useImperativeHandle を使わない。 React ラッパーは useEffect 内で ref を割り当てますが、これは useImperativeHandle のファクトリより後に走ります。ref は直接読んでください。
  3. グリッドを制御コンポーネントにしない。 各セルを React 所有の値として扱うと、キャンバスレンダラーの性能が台無しになります。セルデータの真実の源はグリッドであり、React state は派生値・集計値を保持します。

関連

ニュースレター

開発の最新情報をお届けします

新しいリリース・機能追加・お知らせをいち早く受け取るには、
メーリングリストにご登録ください。