Show HN: React Spreadsheet Component – RowsnColumns
Hello HN, I started working on a new version of React Spreadsheet component - <a href="https:&#x2F;&#x2F;www.rowsncolumns.app" rel="nofollow noreferrer">https:&#x2F;&#x2F;www.rowsncolumns.app</a>. Its faster, highly composable and easily customisable.<p>Feedback welcome :)
Rows n' Columns
Spreadsheet 2 - Composable, declarative Spreadsheet component.Beautifully designed Spreadsheet component for React. Composable. Customizable. DeclarativeCurrent version:@rowsncolumns/spreadsheet 6.2.171@rowsncolumns/spreadsheet-state 16.0.220Built for DevelopersSpreadsheet is rendered in HTML Canvas, giving you ~60fps rendering performance and ability to display millions of rows and columns without peformance impact. With escape hatches, you can access the internals and customize it to your liking.Compose the perfect SpreadsheetPick and choose the components you need to build your Spreadsheet.You can also build own custom functionality on top of Spreadsheet 2, with the many callbacks from CanvasGrid.import { 
  SpreadsheetProvider, CanvasGrid, Toolbar, ButtonUndo, 
  ButtonRedo, RangeSelector, FormulaBarLabel, FormulaBarInput, 
  BottomBar, SheetSwitcher, SheetTabs, SheetStatus 
} from "@rowsncolumns/spreadsheet"

const App = () => {
  const sheet = { id: 1, rowCount: 1000, columnCount: 1000 }
  return (
    <>
      <Toolbar>
        <ButtonUndo onClick={} />
        <ButtonRedo onClick={} />
        {/*..import all other controls*/}
      </Toolbar>
      <FormulaBar>
        <RangeSelector />
        <FormulaBarInput />
      </FormulaBar>
      <CanvasGrid
        sheetId={sheet.id}
        rowCount={sheet.rowCount}
        columnCount={sheet.columnCount}
        getCellData={(sheetId: number, rowIndex: number, columnIndex: number): CellData => {
          return {
            userEnteredValue: {
              formulaValue: '=SUM(4,4)'
            },
            formattedValue: "8"
          }
        }}
      />
      <BottomBar>
        <SheetSwitcher />
        <SheetTabs />
        <SheetStatus />
      </BottomBar>
    </>
  )
}

{/* Wrap your app in SpreadsheetProvider */}
export const Spreadsheet = () => (
  <SpreadsheetProvider>
    <App />
  </SpreadsheetProvider>
)
Bring your own Data model and State managementOr use `useSpreadsheetState` hookSpreadsheet components are all uncontrolled and stateless. It renders based on the props that you pass-in and invokes callbacks to user actions.Spreadsheet 2 is agnostic of your data persistence model. You can choose any database for storage or real-time collaboration.import type { CellData } from "@rowsncolumns/spreadsheet"
import {
  useSpreadsheetState,
  Sheet,
  SheetData,
  RowData
} from "@rowsncolumns/spreadsheet-state"

type MyCellData = CellData & {
  customProperty: 'hello'
}
type SheetData<T extends CellData> = Record<number, RowData<T>[]>
type RowData<T> = {
  values: T[]
}

const App = () => {
  const [ sheets, onChangeSheets ] = useState<Sheet[]>([])
  const [ sheetData, onChangeSheetData ] = useState<SheetData<MyCellData>>({ })
  return (
    <CanvasGrid<MyCellData>
      getCellData={(sheetId, rowIndex, columnIndex) => {
        return sheetData[sheetId]?.[rowIndex]?.values?.[columnIndex]
      }}
      onChange={(value: string, sheetId: number, rowIndex: number, columnIndex: number) => {
        // Persist and generate undo/redo patches
        // If you are using useSpreadsheetState hook,its all built-in
        onChangeSheetData(prevData => ...)
      }}
    />
  )
}

{/* Wrap your app in SpreadsheetProvider */}
export const Spreadsheet = () => (
  <SpreadsheetProvider>
    <App />
  </SpreadsheetProvider>
)
useSpreadsheetState hookThis is an optional (Production grade) hook if you want to get started with the Spreadsheet with complete state management.Uses immer for state management.Full undo/redo capability.Calculation framework with optional web worker support.Real time collaboration built-in.import { 
  defaultSpreadsheetTheme,
  Sheet,
  SheetData,
} from "@rowsncolumns/spreadsheet-state"
import { 
  CellData,
  NamedRange,
  EmbeddedObject,
  EmbeddedChart,
  TableView,
  SpreadsheetTheme,
  CanvasGrid,
  SpreadsheetProvider
} from "@rowsncolumns/spreadsheet"

const App = () => {
  const [sheets, onChangeSheets] = useState<Sheet[]>([]);
  const [sheetData, onChangeSheetData] = useState<SheetData<CellData>>({});
  const [scale, onChangeScale] = useState(1);
  const [colorMode, onChangeColorMode] = useState<ColorMode>();
  const [charts, onChangeCharts] = useState<EmbeddedChart[]>([]);
  const [embeds, onChangeEmbeds] = useState<EmbeddedObject[]>([]);
  const [tables, onChangeTables] = useState<TableView[]>([]);
  const [namedRanges, onChangeNamedRanges] = useState<NamedRange[]>();
  const [theme, onChangeTheme] = useState<SpreadsheetTheme>(defaultSpreadsheetTheme);
  const locale = "en-GB";
  const currency = "USD";
  const {
    activeCell,
    activeSheetId,
    selections,
    rowCount,
    columnCount,
    frozenColumnCount,
    frozenRowCount,
    rowMetadata,
    columnMetadata,
    merges,
    protectedRanges,
    bandedRanges,
    conditionalFormats,
    isDarkMode,
    spreadsheetColors,
    canRedo,
    canUndo,
    undo,
    redo,    
    ...// other Spreadsheet methods
  } = useSpreadsheetState({
    sheets,
    sheetData,
    tables,
    functions,
    namedRanges,
    theme,
    colorMode,
    locale,
    onChangeSheets,
    onChangeSheetData,
    onChangeEmbeds,
    onChangeCharts,
    onChangeTables,
    onChangeNamedRanges,
    onChangeTheme,
  })
  return (
    <>
      <Toolbar>
      <ButtonUndo onClick={onUndo} disabled={!canUndo} />
      <ButtonRedo onClick={onRedo} disabled={!canRedo} />
        {/*..import all other controls*/}
      </Toolbar>

      <CanvasGrid
        {...spreadsheetColors}
        enableTextOverflow
        stickyEditor={true}
        scale={scale}
        conditionalFormats={conditionalFormats}
        sheetId={activeSheetId}
        rowCount={rowCount}
        columnCount={columnCount}
        frozenColumnCount={frozenColumnCount}
        frozenRowCount={frozenRowCount}
        rowMetadata={rowMetadata}
        columnMetadata={columnMetadata}
        activeCell={activeCell}
        selections={selections}
        theme={theme}
        merges={merges}
        charts={charts}
        embeds={embeds}
        tables={tables}
        {/* Other methods from the hook */}
      />
    </>
  )
}

{/* Wrap your app in SpreadsheetProvider */}
export const Spreadsheet = () => (
  <SpreadsheetProvider>
    <App />
  </SpreadsheetProvider>
)
