ReoGrid ReoGrid Web

Sync Grid with React State

ReoGrid Web is an imperative canvas component — it doesn’t re-render on every prop change like a virtual DOM list. The right pattern for integrating with React is a one-way subscription: the grid owns its data, React subscribes to changes.

Full example

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>
  );
}

The total in the header re-renders every time a cell is edited.

Pushing external updates back into the grid

When data changes outside the grid (from a WebSocket, a parent component, etc.) and you need to reflect it:

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]);

Do not drive this from React state that’s already synced from the grid — you’ll create an infinite loop. Only push back from sources external to the grid.

Common pitfalls

  1. Do not call grid methods during render. gridRef.current is null until after useEffect runs. Do setup inside onReady or an effect.
  2. Do not use useImperativeHandle. The React wrapper assigns the ref inside useEffect, which runs after useImperativeHandle’s factory. Read the ref directly.
  3. Do not make the grid a controlled component. Treating each cell as a React-owned value defeats the canvas renderer’s performance. The grid is the source of truth for cell data; React state holds derived/summary values.

See also

Stay Updated

Be first to know — get updates as they ship

Get notified of new releases, features, and announcements.
No spam — just updates that matter.