{"id":13544714,"url":"https://github.com/denisraslov/react-spreadsheet-grid","last_synced_at":"2025-04-14T08:53:07.389Z","repository":{"id":25860676,"uuid":"106738854","full_name":"denisraslov/react-spreadsheet-grid","owner":"denisraslov","description":"An Excel-like grid component for React with custom cell editors, performant scroll \u0026 resizable columns","archived":false,"fork":false,"pushed_at":"2024-03-17T22:19:42.000Z","size":7070,"stargazers_count":1149,"open_issues_count":15,"forks_count":55,"subscribers_count":15,"default_branch":"master","last_synced_at":"2025-04-07T01:08:48.606Z","etag":null,"topics":["data-grid","excel","grid","grid-component","javascript","keyboard","react","spreadsheet","table"],"latest_commit_sha":null,"homepage":"https://denisraslov.github.io/grid","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/denisraslov.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-10-12T19:54:49.000Z","updated_at":"2025-03-17T21:13:38.000Z","dependencies_parsed_at":"2024-01-16T15:51:27.092Z","dependency_job_id":"f461a25a-7700-45c8-914b-1af2cd64ae3e","html_url":"https://github.com/denisraslov/react-spreadsheet-grid","commit_stats":{"total_commits":213,"total_committers":10,"mean_commits":21.3,"dds":"0.41314553990610325","last_synced_commit":"3ab887726c75b084b43f326f09a0a10743fc51eb"},"previous_names":[],"tags_count":15,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/denisraslov%2Freact-spreadsheet-grid","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/denisraslov%2Freact-spreadsheet-grid/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/denisraslov%2Freact-spreadsheet-grid/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/denisraslov%2Freact-spreadsheet-grid/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/denisraslov","download_url":"https://codeload.github.com/denisraslov/react-spreadsheet-grid/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248852107,"owners_count":21171839,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["data-grid","excel","grid","grid-component","javascript","keyboard","react","spreadsheet","table"],"created_at":"2024-08-01T11:00:52.613Z","updated_at":"2025-04-14T08:53:07.366Z","avatar_url":"https://github.com/denisraslov.png","language":"JavaScript","funding_links":[],"categories":["JavaScript"],"sub_categories":[],"readme":"# React Spreadsheet Grid\n\u003e An Excel-like grid component for React with custom cell editors, performant scroll \u0026 resizable columns\n\n![react-spreadsheet-grid in action](https://raw.githubusercontent.com/denisraslov/react-spreadsheet-grid/master/demo.gif)\n\n## The key features\n\nThis is an Excel-like Spreadsheet Grid component that supports:\n\n✅  Custom cell editors (use built-in Input and Select, or any other components) \u0026 header content\n\n✅  Performant scroll for as many rows as you need\n\n✅  Resizable columns\n\n✅  Control by mouse \u0026 from keyboard\n\n✅  Flexible setting of disabled cells\n\n✅  Lazy loading support\n\n✅  Customizable CSS styling\n\n✅  Hooks compatible\n\n✅  TypeScript compatible\n\n## Table of contents\n\n-   [Live playground](#live-playground)\n-   [Installation](#installation)\n-   [A primitive example](#a-primitive-example)\n-   [The pattern of regular usage](#the-pattern-of-regular-usage)\n-   [Props](#props)\n-   [Public methods](#public-methods)\n-   [Customizing cells \u0026 header content](#customizing-cells--header-content)\n    -   [Built-in Input](#built-in-input)\n    -   [Built-in Select](#built-in-select)\n    -   [Another component](#another-component)\n-   [Performant scroll](#performant-scroll)\n-   [Resizable columns](#resizable-columns)\n-   [Control by mouse \u0026 from keyboard](#control-by-mouse--from-keyboard)\n-   [Lazy loading support](#lazy-loading-support)\n-   [Customizing CSS styles](#customizing-css-styles)\n\n## Live playground\n\nFor examples of the grid in action, you can run the demo on your own computer:\n\n* Clone this repository\n* `npm install`\n* `npm run storybook`\n* Visit http://localhost:6006/\n\n## Installation\n\nThis module is distributed via [npm](https://www.npmjs.com/) and should be installed as one of your project's `dependencies`:\n\n```\nnpm install --save react-spreadsheet-grid\n```\n\n\u003e ⚠️ **IMPORTANT!** This package also depends on `react`, `react-dom` and `prop-types`. Please make sure you have those installed as well.\n\n## A primitive example\n\n```jsx\nimport React, { useState } from 'react'\nimport { Grid, Input, Select } from 'react-spreadsheet-grid'\n\nconst rows = [\n    { id: 'user1', name: 'John Doe', positionId: 'position1' },\n    // and so on...\n];\n\nconst MyAwesomeGrid = () =\u003e {\n  return (\n    \u003cGrid\n      columns={[\n        {\n          title: () =\u003e 'Name',\n          value: (row, { focus }) =\u003e {\n              return (\n                  \u003cInput\n                    value={row.name}\n                    focus={focus}\n                  /\u003e\n              );\n          }\n        }, {\n          title: () =\u003e 'Position',\n          value: (row, { focus }) =\u003e {\n              return (\n                  \u003cSelect\n                    value={row.positionId}\n                    isOpen={focus}\n                    items={somePositions}\n                  /\u003e\n              );\n          }\n        }\n      ]}\n      rows={rows}\n      getRowKey={row =\u003e row.id}\n    /\u003e\n  )\n}\n```\n\n## The pattern of regular usage\n\nTake a closer look at 2 main thing: **a declaration of columns** and **work with the state of the parent component**.\n\nTo get the correct behavior of the grid you should:\n\n* Store rows and columns of the grid in the state of the parent component.\n* Describe how the grid renders values of the cells.\n* Have a callback that changes values of the rows in the state of the parent component.\n\nLet's see how it works:\n\n```jsx\nimport { Grid, Input, Select } from 'react-spreadsheet-grid'\nimport AwesomeAutocomplete from 'awesome-autocomplete'\n\nconst rows = [\n    { id: 'user1', name: 'John Doe', positionId: 'position1', managerId: 'manager1' },\n    // and so on...\n];\n\nconst MyAwesomeGrid = () =\u003e {\n    // Rows are stored in the state.\n    const [rows, setRows] = useState(rows);\n\n    // A callback called every time a value changed.\n    // Every time it save a new value to the state.\n    const onFieldChange = (rowId, field) =\u003e (value) =\u003e {\n        // Find the row that is being changed\n        const row = rows.find({ id } =\u003e id === rowId);\n        \n        // Change a value of a field\n        row[field] = value;\n        setRows([].concat(rows))\n    }\n    \n    const initColumns = () =\u003e [\n      {\n        title: () =\u003e 'Name',\n        value: (row, { focus }) =\u003e {\n          // You can use the built-in Input.\n          return (\n            \u003cInput\n              value={row.name}\n              focus={focus}\n              onChange={onFieldChange(row.id, 'name')}\n            /\u003e\n          );\n        }\n      }, {\n        title: () =\u003e 'Position',\n        value: (row, { focus }) =\u003e {\n            // You can use the built-in Select.\n            return (\n                \u003cSelect\n                  value={row.positionId}\n                  isOpen={focus}\n                  items={somePositions}\n                  onChange={onFieldChange(row.id, 'positionId')}\n                /\u003e\n            );\n        }\n      }, {\n        title: () =\u003e 'Manager',\n        value: (row, { active, focus }) =\u003e {\n          // You can use whatever component you want to change a value.\n          return (\n            \u003cAwesomeAutocomplete\n              value={row.managerId}\n              active={active}\n              focus={focus}\n              onSelectItem={onFieldChange(row.id, 'managerId')}\n            /\u003e\n          );\n        }\n      }\n    ]\n\n    return (\n        \u003cGrid\n            columns={initColumns()}\n            rows={rows}\n            isColumnsResizable\n            onColumnResize={onColumnResize}\n            getRowKey={row =\u003e row.id}\n        /\u003e\n    )\n}\n```\n\n## Props\n\n### columns\n\n```jsx\narrayOf({\n    id: string / number,\n    title: string / func,\n    value: string / func(row, { active, focus, disabled }),\n    width: number,\n    getCellClassName: func(row)\n})\n```\n\u003e defaults to `[]`\n\n\u003e `required`\n\nThis is the most important prop that defines columns of the table. Every item of the array is responsible for the corresponding column.\n\nkey | Required | Mission\n--- | --- | ---\n`id` | yes | An identifier of a row.\n`title` | yes | This is what you want to put in the header of the column, it could be passed as a string or as a func returning a React element.\n`value` | yes | This is content of the cell. Works the same way as `title`, but func receives `row` and current state of the cell (`{ active, focus, disabled }`) as parameters, so you can create an output based on them.\n`width` | no | Pass this property if you want to initialize the width of a column. You can set width not for all the columns, then the rest of the table width would be distributed between the columns with unspecified width. Also, you can get width of the columns from `onColumnResize` callback to store somewhere and use for the next render to make columns stay the same width.\n`getCellClassName` | no | An additional class name getter for a row.\n\n### rows\n\u003e `arrayOf(any)` | defaults to `[]`\n\n\u003e `required`\n\nThis is an array of rows for the table. Every row will be passed to a `column.value` func (if you use it).\n\n### getRowKey\n\u003e `func(row)`\n\n\u003e `required`\n\nThis is a func that must return *unique* key for a row based on this row in a parameter.\n\n### placeholder\n\u003e `string` | defaults to `\"There are no rows\"`\n\nUsed as a placeholder text when the `rows` array is empty.\n\n### disabledCellChecker\n\u003e `func(row, columnId): bool`\n\nUse this func to define what cells are disabled in the table. It gets `row` and `columnId` (defined as `column.id` in a `columns` array) as parameters and identifiers of a cell. It should return boolean `true / false`. A disabled cell gets special CSS-class and styles. Also, you can define a `column.value` output based on the `disabled` state parameter.\n\n### onCellClick\n\u003e `func(row, columnId)`\n\nA click handler function for a cell. It gets `row` and `columnId` (defined as `column.id` in the `columns` array) as parameters and identifiers of a cell.\n\n### onActiveCellChanged\n\u003e `func({ x, y })`\n\nA callback called every time the active cell is changed. It gets `{ x, y }` coordinates of the new active cell as parameters.\n\n### headerHeight\n\u003e `number` | defaults to `40`\n\nThe height of the header of the table in pixels.\n\n⚠️ Define it as a prop, not in CSS styles to not broke the scroll of the table. ⚠️\n\n### rowHeight\n\u003e `number` | defaults to `48`\n\nThe height of a row of the table in pixels.\n\n⚠️ Define it as a prop, not in CSS styles to not broke the scroll of the table. ⚠️\n\n### focusOnSingleClick\n\u003e `boolean`\n\n\u003e defaults to `false`\n\nBy default, double clicking a cell sets the focus on the cell's input. Pass `true` if you want to set the focus on the cell's input upon single clicking it.\n\n### isColumnsResizable\n\u003e `bool` | defaults to `false`\n\nSwitch this on if you want the table provides an opportunity to resize column width.\n\n### onColumnResize\n\u003e `func(widthValues: object)`\n\nA callback called every time the width of a column was resized. Gets `widthValues` object as a parameter. `widthValues` is a map of values of width for all the columns in percents (`columnId` - `value`).\n\n### isScrollable\n\u003e `boolean`\n\n\u003e defaults to `true`\n\nThis defines should a grid has a scrollable container inside of a DOM-element where it was rendered, or not. When it turned on (by default), only visible rows are rendered and that improves performance. If you pass `false`, all the rows will be rendered at once (that is not a good way to handle with a big amount of them), but you will have opportunity to set up a scroll area where you want it to be and have other components (before or after the grid) included in this area.\n\n### onScroll\n\u003e `func(scrollPosition: number)`\n\nA callback called every time the position of the scroll of the grid was changed.\n\n### onScrollReachesBottom\n\u003e `func()`\n\nA callback called when the scroll of the grid reaches its bottom value. Usually, it could be used to implement the lazy loading feature in your grid (see the [Lazy loading support](#the-pattern-of-regular-usage) section for details).\n\n## Public methods\n\nUse public methods via a grid's ref:\n\n```jsx\nconst GridWrapper = () =\u003e {\n  const gridRef = React.createRef()\n\n  React.useEffect(() =\u003e {\n    gridRef.current.resetScroll()\n  })\n\n  return (\n    \u003cGrid\n      ref={gridRef}\n      // other props\n    /\u003e\n  )\n}\n```\n\n### resetScroll()\n\nCall to reset the scroll to the top of the container.\n\n### focusCell({ x: number, y: number })\n\nCall to make the cell with this `x, y` coordinates (starting from `0`) active and focused.\n\n## Customizing cells \u0026 header content\n\nYou can customize content of titles and cells using `title` and `value` keys of elements of the `columns` property. Setting these components using `row` and `{ active, focus, disabled }` parameters of the functions.\n\n`title` could be a string or a func returning any React element.\n\n`value` works the same way, but func receives current `row` and current state of the cell (`{ active, focused, disabled }`) as parameters, so you can create an output based on them.\n\nFor the basic usage, the library provide 2 default components that you can use out-of-the-box: `Input` and `Select`. Perhaps, they will be enough for you. However, you can use any other React components for that purpose: autocompletes, checkboxes, etc.\n\n### Built-in Input\n\n`Input` prop types:\n\nProp | Type | Mission\n--- | --- | ---\n`value` | string | The value of the input\n`placeholder` | string | Placeholder displaying when there is no value\n`focus` | bool | Should the input has focus or not\n`selectTextOnFocus` | bool | Should the input content be selected when focused or not\n`onChange` | func | Blur callback. Use it to catch a changed value\n\nUsage:\n\n```jsx\nimport { Grid, Input } from 'react-spreadsheet-grid'\n\n \u003cGrid\n    columns={[\n      {\n        id: 'name',\n        title: () =\u003e {\n            return \u003cspan\u003eName\u003c/span\u003e\n        },\n        value: (row, { focus }) =\u003e {\n          return (\n            \u003cInput\n              value={row.name}\n              focus={focus}\n              onChange={onFieldChange(row.id, 'name')}\n            /\u003e\n          );\n        }\n      }\n   ]}\n/\u003e\n```\n\n### Built-in Select\n\n`Select` prop types:\n\nProp | Type | Mission\n--- | --- | ---\n`items` | arrayOf({ id: string / number, name: string }) | Items for select\n`selectedId` | string / number | Id of a selected item\n`placeholder` | string | Placeholder displaying when there is no selected item\n`isOpen` | bool | Should the select be open or not\n`onChange` | func | Change item callback. Use it to catch a changed value\n\nUsage:\n\n```jsx\nimport { Grid, Select } from 'react-spreadsheet-grid'\n\nconst positions = [{\n    id: 1,\n    name: 'Frontend developer'\n}, {\n    id: 2,\n    name: 'Backend developer'\n}];\n\n \u003cGrid\n    columns={[\n      {\n        id: 'position',\n        title: () =\u003e {\n            return \u003cspan\u003ePosition\u003c/span\u003e\n        },\n        value: (row, { focus }) =\u003e {\n          return (\n            \u003cSelect\n              items={positions}\n              selectedId={row.positionId}\n              isOpen={focus}\n              onChange={onFieldChange(row.id, 'positionId')}\n            /\u003e\n          );\n        }\n      }\n   ]}\n/\u003e\n```\n\n### Another component\n\nLet's suggest you need to use an autocomplete as a content of a cell. This is how it could be done:\n\n```jsx\nimport { Grid } from 'react-spreadsheet-grid'\nimport AwesomeAutocomplete from 'awesome-autocomplete'\n\n\u003cGrid\n  columns={[\n    {\n      id: 'manager',\n      title: () =\u003e {\n        return \u003cspan\u003eManager\u003c/span\u003e\n      },\n      value: (row, { focus, active }) =\u003e {\n        return (\n          \u003cAwesomeAutocomplete\n            value={row.managerId}\n            active={active}\n            focus={focus}\n            onSelectItem={onFieldChange(row.id, 'managerId')}\n          /\u003e\n        );\n      }\n    }\n  ]}\n/\u003e\n```\n\n## Performant scroll\n\nA behavior of scroll depends on the `isScrollable` prop.\n\nIf `isScrollable` is `false`, the grid renders all the passed rows without a scroll. Probably, this would be useful for small amount of the rows.\n\nIf `isScrollable` is `true`, the height of the grid is equal to the height of its container, it has a scroll and renders only the rows that are visible. Therefore, you can pass to it as many rows as you want - it will work fine without any problems with rendering and scroll. This would be useful for big amount of the rows.\n\nThis is an example, how we could make a 500px height scrollable grid:\n\n```jsx\n\u003cdiv style={{ height: '500px' }}\u003e\n    \u003cGrid\n        isScrollable\n        /* other props */\n    /\u003e\n\u003c/div\u003e\n```\n\n## Resizable columns\n\n`react-spreadsheet-grid` provides the opportunity to set initial width values for columns, to resize them from the UI and to react on these changes. Use relevant `columnWidthValues`, `isColumnsResizable` and `onColumnResize` properties for that purpose.\n\nThis is how it could be done:\n\n```jsx\nimport React, { useState } from 'react'\nimport { Grid } from 'react-spreadsheet-grid'\n\nconst ResizableGrid = () =\u003e {\n    // Put columns to the state to be able to store there their width values.\n    const [columns, setColumns] = useState(initColumns())\n\n    // Change columns width values in the state to not lose them.\n    const onColumnResize = (widthValues) =\u003e {\n        const newColumns = [].concat(columns)\n        Object.keys(widthValues).forEach((columnId) =\u003e {\n            newColumns[columnId].width = widthValues[columnId]\n        })\n        setColumns(newColumns)\n    }\n\n    return (\n        \u003cGrid\n            columns={columns}\n            isColumnsResizable\n            onColumnResize={onColumnResize}\n            rows={/* some rows here */}\n            getRowKey={row =\u003e row.id}\n        /\u003e\n    )\n}\n```\n\n## Control by mouse \u0026 from keyboard\n\n`react-spreadsheet-grid` could be controlled by a mouse and from keyboard (just like Excel-table could). When a mouse is used, single click make a cell `active`, double click make a cell `focused`. When a keyboard used, `←` `→` `↑` `↓` move `active` cell, `ENTER` and `TAB` make a cell `focused`.\n\n## Customizing CSS styles\n\nRight now, the easiest way to tweak `react-spreadsheet-grid` is to create another stylesheet to override the default styles. For example, you could create a file named `react_spreadsheet_grid_overrides.css` with the following contents:\n\n```css\n.SpreadsheetGrid__cell_active {\n    box-shadow: inset 0 0 0 2px green;\n}\n```\n\nThis would override the color of borders for the table active cell.\n\n⚠️ The only exception, that you have to use `headerHeight` and `rowHeight` props to redefine height of the header and rows to not broke the scroll of the table.\n\n## Lazy loading support\n\n`react-spreadsheet-grid` provides the opportunity to implement the lazy loading feature in your grid. Use the `onScrollReachesBottom` callback to handle a situation when the scroll position reaches its bottom. Load a new portion of the rows and put them in the state of a high-order component.\n\nThis is how it could be done:\n\n```jsx\nimport React, { useState } from 'react'\nimport { Grid } from 'react-spreadsheet-grid'\n\nconst LazyLoadingGrid = () =\u003e {\n  /* Init the state with the initial portion of the rows */\n  const [rows, setRows] = useState(initialRows);\n\n  const onScrollReachesBottom = () =\u003e {\n     loadNewPortionOfRows().then((newRows) =\u003e {\n        setRows(rows.concat(newRows));\n     });\n  }\n\n  const loadNewPortionOfRows = () =\u003e {\n    /* an ajax request here */\n  }\n\n  return (\n      \u003cGrid\n        columns={/* some columns here */}\n        row={rows}\n        getRowKey={row =\u003e row.id}\n        onScrollReachesBottom={onScrollReachesBottom}\n      /\u003e\n    )\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdenisraslov%2Freact-spreadsheet-grid","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdenisraslov%2Freact-spreadsheet-grid","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdenisraslov%2Freact-spreadsheet-grid/lists"}