react-datasheet-grid

repository·master·Indexed 24 days ago

https://github.com/nick-keller/react-datasheet-grid

An Excel and Airtable-like spreadsheet component for React designed for high performance with virtualization. It supports hundreds of thousands of rows, keyboard navigation, copy/paste from Excel, and custom widgets. Key features include the DataSheetGrid and DynamicDataSheetGrid components, built-in column types like checkboxColumn and textColumn, and customizable context menus and row-addition components.

Tokens
19.5K
Snippets
47
Records
99
Agent score
80%

What's inside react-datasheet-grid

  1. Understand the static behavior of DataSheetGrid

    master

    By default, <DataSheetGrid /> is static. This means it captures the props it receives during the first render and ignores subsequent changes to non-primitive props. This behavior is designed to prevent unnecessary re-renders when using inline objects, arrays, or functions (e.g., defining columns or createRow directly in the JSX).

    Because of this, you can safely use inline props without performance penalties, but those props will not update the grid if they change later in the component lifecycle.

    import { DataSheetGrid } from 'react-datasheet-grid'
    
    const MyComponent = () => {
      const [ data, setData ] = useState([])
    
      return (
        <DataSheetGrid
          value={data}
          onChange={setData}
          columns={[
            {/*...*/},
            {/*...*/},
          ]}
          createRow={() => ({ id: genId() })}
        />
      )
    }
  2. Control component interaction using CellProps

    master

    Use the following properties from CellProps to synchronize your custom component with the grid's state:

    • focus: Use this to enable/disable pointerEvents (e.g., pointerEvents: focus ? undefined : 'none') so the component only interacts when the cell is focused. Also use it to control whether a menu is open (e.g., menuIsOpen={focus}).
    • active: Use this to show/hide UI elements like placeholders or caret indicators only when the cell is active.
    • rowData: Use this to bind the current cell value to your component's value prop.
    • setRowData: Call this to update the grid's underlying data when the component's value changes.
    • stopEditing: Call this to exit edit mode. When using portals, you may need to call stopEditing({ nextRow: false }) on menu close to prevent the grid from blurring the cell prematurely.
  3. How columns work in react-datasheet-grid

    master
    DSG is a column-based grid. This means all cells within a single column share the same data type and the same widget (UI component). Unlike Excel, where cells can be independent, DSG behaves more like Notion or Airtable where the column definition dictates the behavior for the entire vertical slice of data.
  4. Sync grid operations with nested data

    master

    When implementing collapsible rows, you must manually map DataSheetGrid operations back to your nested source of truth. The onChange callback provides an operations array containing type, fromRowIndex, and toRowIndex.

    To maintain data integrity:

    • UPDATE: Iterate through the affected rows in newRows. If the row type is 'CHILD', use its groupIndex and childIndex to update the specific object in your nested array.
    • CREATE: Identify the parent group using the index of the row preceding the new rows. Insert the new child objects into the nested children array at the correct position.
    • DELETE: Iterate through the deleted rows in reverse order. For every row with type: 'CHILD', use splice on the nested children array at the specified childIndex.
  5. Optimize cell renders with `React.memo()`

    master

    To prevent unnecessary re-renders in the grid, wrap your custom cell components in React.memo(). This ensures that a component only re-renders when its props actually change. Note that for extremely lightweight components, the overhead of a props check might be slower than a simple re-render, but for most custom components, React.memo() is recommended.

    const MyComponent = React.memo(({ rowData, setRowData }) => {
      return <input {/*...*/} />
    })
    
    const column = { component: MyComponent, /*...*/ }
  6. Handle copy and paste for custom columns

    master

    To support copy-pasting in a custom column component, you must implement three specific properties in your column configuration:

    1. deleteValue: A function called when a cell is cut or deleted. Return the value you want the cell to hold after deletion (e.g., () => null).
    2. copyValue: A function called when a cell is copied. It receives an object containing rowData. You should return the value you want to appear on the user's clipboard (e.g., a human-readable label).
    3. pasteValue: A function called when a value is pasted. It receives the clipboard value and should transform it back into the underlying data type used by the grid.

    This is particularly useful for select components where you might want to copy the visible label but paste the underlying ID/value.

    function App() {
      const [ data, setData ] = useState(['chocolate', 'strawberry', null])
    
      return (
        <DataSheetGrid
          value={data}
          onChange={setData}
          columns={[
            {
              component: SelectComponent,
              disableKeys: true,
              deleteValue: () => null,
              copyValue: ({ rowData }) =>
                choices.find((choice) => choice.value === rowData)?.label,
              pasteValue: ({ value }) =>
                choices.find((choice) => choice.label === value)?.value ?? null,
              title: 'Flavor',
            },
          ]}
        />
      )
    }
  7. Implement a custom AddRowsComponent

    master

    If you need full control over the UI of the 'Add rows' section, you can implement the AddRowsComponentProps interface yourself. The component receives an addRows function which can be called as addRows() to add a single row or addRows(n) to add n rows.

    import { AddRowsComponentProps } from 'react-datasheet-grid'
    
    function AddRows({ addRows }: AddRowsComponentProps) {
      // You can call addRows() or addRows(n) where n is the number of rows
      return /*...*/
    }
    
    function App() {
      return (
        <DataSheetGrid
          addRowsComponent={AddRows}
        />
      )
    }
  8. Basic usage of DataSheetGrid with built-in columns

    master

    To implement a basic grid, use the DataSheetGrid component. You must provide a value (the data array), an onChange handler to update that data, and a columns configuration array.

    Columns are typically created using helper functions like keyColumn combined with column type helpers like textColumn or checkboxColumn.

    Important: You must import the component's CSS once in your application to ensure correct styling: import 'react-datasheet-grid/dist/style.css'

    import React, { useState } from 'react'
    import {
      DataSheetGrid,
      checkboxColumn,
      textColumn,
      keyColumn,
    } from 'react-datasheet-grid'
    
    // Import the style only once in your app!
    import 'react-datasheet-grid/dist/style.css'
    
    const Example = () => {
      const [ data, setData ] = useState([
        { active: true, firstName: 'Elon', lastName: 'Musk' },
        { active: false, firstName: 'Jeff', lastName: 'Bezos' },
      ])
    
      const columns = [
        { ...keyColumn('active', checkboxColumn), title: 'Active' },
        { ...keyColumn('firstName', textColumn), title: 'First name' },
        { ...keyColumn('lastName', textColumn), title: 'Last name' },
      ]
    
      return (
        <DataSheetGrid
          value={data}
          onChange={setData}
          columns={columns}
        />
      )
    }
  9. Use TypeScript with react-datasheet-grid

    master

    The library is written in TypeScript and exports all types directly. To get full type safety and IDE autocompletion, you should define a type for your row data and use it when defining your columns and the DataSheetGrid component.

    import {
      checkboxColumn,
      Column,
      DataSheetGrid,
      keyColumn,
      textColumn,
    } from 'react-datasheet-grid'
    import 'react-datasheet-grid/dist/style.css'
    
    // Define your row type
    type Row = {
      active: boolean
      firstName: string | null
      lastName: string | null
    }
    
    function App() {
      const [data, setData] = useState<Row[]>([
        { active: true, firstName: 'Elon', lastName: 'Musk' },
        { active: false, firstName: 'Jeff', lastName: 'Bezos' },
      ])
    
      // Type your columns using Column<Row> to get type checks
      const columns: Column<Row>[] = [
        {
          ...keyColumn<Row, 'active'>('active', checkboxColumn),
          title: 'Active',
        },
        {
          ...keyColumn<Row, 'firstName'>('firstName', textColumn),
          title: 'First name',
    n    },
        {
          ...keyColumn<Row, 'lastName'>('lastName', textColumn),
          title: 'Last name',
        },
      ]
    
      return (
        <DataSheetGrid
          value={data}
          onChange={setData}
          columns={columns}
        />
      )
    }