Multi-Instance Spreadsheets

Run multiple spreadsheet instances in the same application

The SpreadsheetMultiInstanceProvider component allows you to render multiple spreadsheet instances within the same application while maintaining isolated state and preventing conflicts between instances.

Overview

By default, each spreadsheet instance shares global context through SpreadsheetProvider. When you need to render multiple spreadsheets simultaneously (e.g., side-by-side comparison, multi-document interface), you need to wrap them in SpreadsheetMultiInstanceProvider to ensure proper isolation.

Basic Usage

import {
  SpreadsheetProvider,
  SpreadsheetMultiInstanceProvider,
  CanvasGrid,
} from "@rowsncolumns/spreadsheet";
import { useSpreadsheetState } from "@rowsncolumns/spreadsheet-state";

function MultiSpreadsheetApp() {
  return (
    <SpreadsheetMultiInstanceProvider>
      <SpreadsheetProvider>
        <SpreadsheetInstance instanceId="spreadsheet-1" />
      </SpreadsheetProvider>

      <SpreadsheetProvider>
        <SpreadsheetInstance instanceId="spreadsheet-2" />
      </SpreadsheetProvider>
    </SpreadsheetMultiInstanceProvider>
  );
}

function SpreadsheetInstance({ instanceId }: { instanceId: string }) {
  const {
    activeCell,
    activeSheetId,
    selections,
    // ... other state
  } = useSpreadsheetState({
    // configuration
  });

  return (
    <CanvasGrid
      instanceId={instanceId}
      sheetId={activeSheetId}
      activeCell={activeCell}
      selections={selections}
      // ... other props
    />
  );
}

Instance ID

Each CanvasGrid instance should have a unique instanceId prop when rendered within SpreadsheetMultiInstanceProvider:

<CanvasGrid
  instanceId="spreadsheet-a"  // Unique identifier
  sheetId={activeSheetId}
  // ... other props
/>

Why Instance IDs Matter

Instance IDs ensure that:

  • Keyboard events are routed to the correct spreadsheet

  • Editor state is isolated between instances

  • Context menus and dialogs appear in the correct location

  • Formula evaluation doesn't cross instance boundaries

Use Cases

Side-by-Side Comparison

Compare two spreadsheets side by side:

function SpreadsheetComparison() {
  return (
    <SpreadsheetMultiInstanceProvider>
      <div className="flex gap-4">
        <div className="flex-1">
          <h2>Version 1</h2>
          <SpreadsheetProvider>
            <SpreadsheetView instanceId="version-1" />
          </SpreadsheetProvider>
        </div>

        <div className="flex-1">
          <h2>Version 2</h2>
          <SpreadsheetProvider>
            <SpreadsheetView instanceId="version-2" />
          </SpreadsheetProvider>
        </div>
      </div>
    </SpreadsheetMultiInstanceProvider>
  );
}

Multi-Document Interface

Create a tabbed or windowed interface with multiple spreadsheet documents:

function MultiDocumentInterface() {
  const [documents] = useState([
    { id: "doc-1", name: "Budget 2024" },
    { id: "doc-2", name: "Sales Report" },
    { id: "doc-3", name: "Inventory" },
  ]);

  return (
    <SpreadsheetMultiInstanceProvider>
      {documents.map((doc) => (
        <SpreadsheetProvider key={doc.id}>
          <div className="document-window">
            <h3>{doc.name}</h3>
            <SpreadsheetView instanceId={doc.id} />
          </div>
        </SpreadsheetProvider>
      ))}
    </SpreadsheetMultiInstanceProvider>
  );
}

Master-Detail View

Display a master spreadsheet with a detail view:

function MasterDetailView() {
  const [selectedRow, setSelectedRow] = useState<number | null>(null);

  return (
    <SpreadsheetMultiInstanceProvider>
      <div className="grid grid-cols-2 gap-4">
        {/* Master spreadsheet */}
        <SpreadsheetProvider>
          <h2>Sales Overview</h2>
          <SpreadsheetView
            instanceId="master"
            onRowSelect={setSelectedRow}
          />
        </SpreadsheetProvider>

        {/* Detail spreadsheet */}
        {selectedRow && (
          <SpreadsheetProvider>
            <h2>Row Details</h2>
            <SpreadsheetView
              instanceId="detail"
              rowData={selectedRow}
            />
          </SpreadsheetProvider>
        )}
      </div>
    </SpreadsheetMultiInstanceProvider>
  );
}

Complete Example

import React, { useState } from "react";
import {
  SpreadsheetProvider,
  SpreadsheetMultiInstanceProvider,
  CanvasGrid,
  Sheet,
} from "@rowsncolumns/spreadsheet";
import {
  useSpreadsheetState,
  SheetData,
  CellData,
} from "@rowsncolumns/spreadsheet-state";

function App() {
  return (
    <SpreadsheetMultiInstanceProvider>
      <div className="flex gap-5 h-screen p-5">
        <div className="flex-1">
          <SpreadsheetProvider>
            <SpreadsheetA />
          </SpreadsheetProvider>
        </div>

        <div className="flex-1">
          <SpreadsheetProvider>
            <SpreadsheetB />
          </SpreadsheetProvider>
        </div>
      </div>
    </SpreadsheetMultiInstanceProvider>
  );
}

function SpreadsheetA() {
  const [sheets, setSheets] = useState<Sheet[]>([
    { sheetId: 1, rowCount: 100, columnCount: 26, title: "Sheet A" }
  ]);
  const [sheetData, setSheetData] = useState<SheetData<CellData>>({});

  const {
    activeCell,
    activeSheetId,
    selections,
    getCellData,
    onChangeActiveCell,
    onChangeSelections,
    onChange,
  } = useSpreadsheetState({
    sheets,
    sheetData,
    onChangeSheets: setSheets,
    onChangeSheetData: setSheetData,
  });

  return (
    <CanvasGrid
      instanceId="spreadsheet-a"
      sheetId={activeSheetId}
      activeCell={activeCell}
      selections={selections}
      getCellData={getCellData}
      onChangeActiveCell={onChangeActiveCell}
      onChangeSelections={onChangeSelections}
      onChange={onChange}
    />
  );
}

function SpreadsheetB() {
  // Similar implementation with different instance ID
  // ...

  return (
    <CanvasGrid
      instanceId="spreadsheet-b"
      // ... props
    />
  );
}

Best Practices

  1. Always Use Unique Instance IDs: Ensure each spreadsheet has a unique instanceId to prevent conflicts

  2. Wrap Each Instance: Each spreadsheet should have its own SpreadsheetProvider wrapper

  3. Isolate State: Use separate state management for each spreadsheet instance

  4. Performance Considerations: Be mindful of rendering multiple large spreadsheets simultaneously - consider lazy loading or virtualization

  5. Memory Management: Clean up instances when they're no longer needed to free up resources

Limitations

  • Each instance maintains its own calculation engine and state

  • Cross-instance formulas are not supported (formulas cannot reference cells from other instances)

  • Each instance requires separate data management

Performance Tips

When rendering multiple instances:

// Use React.memo to prevent unnecessary re-renders
const SpreadsheetInstance = React.memo(({ instanceId, data }) => {
  // ... implementation
});

// Lazy load instances that aren't immediately visible
const LazySpreadsheet = lazy(() => import('./SpreadsheetInstance'));

function MultiInstance() {
  return (
    <SpreadsheetMultiInstanceProvider>
      <Suspense fallback={<div>Loading...</div>}>
        <LazySpreadsheet instanceId="lazy-1" />
      </Suspense>
    </SpreadsheetMultiInstanceProvider>
  );
}

Troubleshooting

Keyboard Events Not Working

If keyboard events aren't working properly, ensure:

  • Each CanvasGrid has a unique instanceId

  • The SpreadsheetMultiInstanceProvider wraps all instances

  • Only one spreadsheet has focus at a time

State Conflicts

If you experience state conflicts between instances:

  • Verify each instance has its own SpreadsheetProvider

  • Check that state management is properly isolated

  • Ensure instance IDs are unique and don't change during component lifecycle

Last updated

Was this helpful?