グリッドを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 でこれを駆動してはいけません。無限ループになります。書き戻すのは、グリッドの外部にあるソースからのみにしてください。
よくある落とし穴
- レンダー中にグリッドのメソッドを呼ばない。
gridRef.currentはuseEffectが走るまで null です。セットアップはonReadyか effect の中で行ってください。 useImperativeHandleを使わない。 React ラッパーはuseEffect内で ref を割り当てますが、これはuseImperativeHandleのファクトリより後に走ります。ref は直接読んでください。- グリッドを制御コンポーネントにしない。 各セルを React 所有の値として扱うと、キャンバスレンダラーの性能が台無しになります。セルデータの真実の源はグリッドであり、React state は派生値・集計値を保持します。