DEV Community

Michael Turner
Michael Turner

Posted on

Getting Started with ReactGrid in React: Building Your First Spreadsheet

ReactGrid is a powerful, flexible spreadsheet component for React that provides Excel-like functionality with cell editing, formulas, and data manipulation. It's designed to be lightweight and easy to use while offering essential spreadsheet features for building data-intensive applications. This guide walks through setting up and creating your first interactive spreadsheet using ReactGrid with React, from installation to a working implementation. This is part 7 of a series on using ReactGrid with React.

Prerequisites

Before you begin, make sure you have:

  • Node.js version 14.0 or higher installed
  • npm, yarn, or pnpm package manager
  • A React project (version 16.8 or higher) or create-react-app setup
  • Basic knowledge of React hooks (useState)
  • Familiarity with JavaScript/TypeScript

Installation

Install ReactGrid using your preferred package manager:

npm install @silevis/reactgrid
Enter fullscreen mode Exit fullscreen mode

Or with yarn:

yarn add @silevis/reactgrid
Enter fullscreen mode Exit fullscreen mode

Or with pnpm:

pnpm add @silevis/reactgrid
Enter fullscreen mode Exit fullscreen mode

After installation, your package.json should include:

{
  "dependencies": {
    "@silevis/reactgrid": "^7.0.0",
    "react": "^18.0.0",
    "react-dom": "^18.0.0"
  }
}
Enter fullscreen mode Exit fullscreen mode

Project Setup

ReactGrid requires CSS styles to be imported. Add the styles to your main application file:

// src/index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import '@silevis/reactgrid/styles.css';
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);
Enter fullscreen mode Exit fullscreen mode

First Example / Basic Usage

Let's create a simple spreadsheet component. Create a new file src/Spreadsheet.jsx:

// src/Spreadsheet.jsx
import React, { useState } from 'react';
import ReactGrid from '@silevis/reactgrid';
import '@silevis/reactgrid/styles.css';

function Spreadsheet() {
  // Define the initial data structure
  const [data, setData] = useState(() => {
    const rows = [
      [
        { type: 'header', text: 'Name' },
        { type: 'header', text: 'Age' },
        { type: 'header', text: 'City' }
      ],
      [
        { type: 'text', text: 'John Doe' },
        { type: 'number', value: 25 },
        { type: 'text', text: 'New York' }
      ],
      [
        { type: 'text', text: 'Jane Smith' },
        { type: 'number', value: 30 },
        { type: 'text', text: 'London' }
      ],
      [
        { type: 'text', text: 'Bob Johnson' },
        { type: 'number', value: 35 },
        { type: 'text', text: 'Paris' }
      ]
    ];
    return rows;
  });

  // Handle cell changes
  const handleChanges = (changes) => {
    setData((prevData) => {
      const newData = [...prevData];
      changes.forEach(({ rowId, columnId, newCell }) => {
        newData[rowId][columnId] = newCell;
      });
      return newData;
    });
  };

  return (
    <div style={{ height: '400px', width: '100%' }}>
      <ReactGrid
        rows={data}
        enableRangeSelection
        enableFillHandle
        enableRowSelection
        onChanges={handleChanges}
      />
    </div>
  );
}

export default Spreadsheet;
Enter fullscreen mode Exit fullscreen mode

Now, update your App.jsx to use the spreadsheet:

// src/App.jsx
import React from 'react';
import Spreadsheet from './Spreadsheet';
import './App.css';

function App() {
  return (
    <div className="App" style={{ padding: '20px' }}>
      <h1>ReactGrid Example</h1>
      <Spreadsheet />
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

This creates a basic spreadsheet with three columns (Name, Age, City) and three rows of data. You can click on cells to edit them, and the data will update in real-time.

Understanding the Basics

ReactGrid uses a cell-based data structure where each cell is an object with:

  • type: The cell type ('text', 'number', 'header', etc.)
  • text: Text content for text cells
  • value: Numeric value for number cells
  • style: Optional styling properties

Key concepts:

  • Rows: Array of row arrays, where each row contains cell objects
  • Cell Types: Different cell types for different data (text, number, date, etc.)
  • Changes Handler: Function that receives cell changes and updates the data
  • Features: Enable features like range selection, fill handle, and row selection

Here's a simpler example with just text data:

// src/SimpleSpreadsheet.jsx
import React, { useState } from 'react';
import ReactGrid from '@silevis/reactgrid';
import '@silevis/reactgrid/styles.css';

function SimpleSpreadsheet() {
  const [data, setData] = useState([
    [
      { type: 'header', text: 'A' },
      { type: 'header', text: 'B' },
      { type: 'header', text: 'C' }
    ],
    [
      { type: 'text', text: 'A1' },
      { type: 'text', text: 'B1' },
      { type: 'text', text: 'C1' }
    ],
    [
      { type: 'text', text: 'A2' },
      { type: 'text', text: 'B2' },
      { type: 'text', text: 'C2' }
    ]
  ]);

  const handleChanges = (changes) => {
    setData((prevData) => {
      const newData = [...prevData];
      changes.forEach(({ rowId, columnId, newCell }) => {
        newData[rowId][columnId] = newCell;
      });
      return newData;
    });
  };

  return (
    <div style={{ height: '300px', width: '100%' }}>
      <ReactGrid rows={data} onChanges={handleChanges} />
    </div>
  );
}

export default SimpleSpreadsheet;
Enter fullscreen mode Exit fullscreen mode

Practical Example / Building Something Real

Let's build a budget tracker spreadsheet with calculations:

// src/BudgetTracker.jsx
import React, { useState } from 'react';
import ReactGrid from '@silevis/reactgrid';
import '@silevis/reactgrid/styles.css';

function BudgetTracker() {
  const [data, setData] = useState(() => {
    return [
      [
        { type: 'header', text: 'Category' },
        { type: 'header', text: 'Budgeted' },
        { type: 'header', text: 'Spent' },
        { type: 'header', text: 'Remaining' }
      ],
      [
        { type: 'text', text: 'Food' },
        { type: 'number', value: 500 },
        { type: 'number', value: 320 },
        { type: 'number', value: 180, style: { backgroundColor: '#d4edda' } }
      ],
      [
        { type: 'text', text: 'Transportation' },
        { type: 'number', value: 200 },
        { type: 'number', value: 150 },
        { type: 'number', value: 50, style: { backgroundColor: '#d4edda' } }
      ],
      [
        { type: 'text', text: 'Entertainment' },
        { type: 'number', value: 300 },
        { type: 'number', value: 280 },
        { type: 'number', value: 20, style: { backgroundColor: '#d4edda' } }
      ],
      [
        { type: 'text', text: 'Total', style: { fontWeight: 'bold' } },
        { type: 'number', value: 1000, style: { fontWeight: 'bold' } },
        { type: 'number', value: 750, style: { fontWeight: 'bold' } },
        { type: 'number', value: 250, style: { fontWeight: 'bold', backgroundColor: '#d4edda' } }
      ]
    ];
  });

  const calculateRemaining = (rowIndex) => {
    if (rowIndex === 0 || rowIndex === data.length - 1) return; // Skip header and total row

    const budgeted = data[rowIndex][1]?.value || 0;
    const spent = data[rowIndex][2]?.value || 0;
    const remaining = budgeted - spent;

    setData((prevData) => {
      const newData = [...prevData];
      newData[rowIndex][3] = {
        ...newData[rowIndex][3],
        value: remaining,
        style: {
          backgroundColor: remaining >= 0 ? '#d4edda' : '#f8d7da'
        }
      };
      return newData;
    });
  };

  const calculateTotals = () => {
    let totalBudgeted = 0;
    let totalSpent = 0;

    for (let i = 1; i < data.length - 1; i++) {
      totalBudgeted += data[i][1]?.value || 0;
      totalSpent += data[i][2]?.value || 0;
    }

    const totalRemaining = totalBudgeted - totalSpent;

    setData((prevData) => {
      const newData = [...prevData];
      const lastRow = newData.length - 1;
      newData[lastRow][1] = { ...newData[lastRow][1], value: totalBudgeted };
      newData[lastRow][2] = { ...newData[lastRow][2], value: totalSpent };
      newData[lastRow][3] = {
        ...newData[lastRow][3],
        value: totalRemaining,
        style: {
          backgroundColor: totalRemaining >= 0 ? '#d4edda' : '#f8d7da'
        }
      };
      return newData;
    });
  };

  const handleChanges = (changes) => {
    setData((prevData) => {
      const newData = prevData.map(row => [...row]);
      changes.forEach(({ rowId, columnId, newCell }) => {
        newData[rowId][columnId] = newCell;
      });

      // Recalculate remaining for changed rows
      changes.forEach(({ rowId }) => {
        if (rowId > 0 && rowId < newData.length - 1) {
          const budgeted = newData[rowId][1]?.value || 0;
          const spent = newData[rowId][2]?.value || 0;
          newData[rowId][3] = {
            ...newData[rowId][3],
            value: budgeted - spent,
            style: {
              backgroundColor: (budgeted - spent) >= 0 ? '#d4edda' : '#f8d7da'
            }
          };
        }
      });

      // Recalculate totals
      let totalBudgeted = 0;
      let totalSpent = 0;
      for (let i = 1; i < newData.length - 1; i++) {
        totalBudgeted += newData[i][1]?.value || 0;
        totalSpent += newData[i][2]?.value || 0;
      }
      const lastRow = newData.length - 1;
      newData[lastRow][1] = { ...newData[lastRow][1], value: totalBudgeted };
      newData[lastRow][2] = { ...newData[lastRow][2], value: totalSpent };
      newData[lastRow][3] = {
        ...newData[lastRow][3],
        value: totalBudgeted - totalSpent,
        style: {
          backgroundColor: (totalBudgeted - totalSpent) >= 0 ? '#d4edda' : '#f8d7da'
        }
      };

      return newData;
    });
  };

  return (
    <div style={{ padding: '20px' }}>
      <h2>Monthly Budget Tracker</h2>
      <div style={{ height: '400px', width: '100%', border: '1px solid #ccc' }}>
        <ReactGrid
          rows={data}
          enableRangeSelection
          enableFillHandle
          onChanges={handleChanges}
        />
      </div>
    </div>
  );
}

export default BudgetTracker;
Enter fullscreen mode Exit fullscreen mode

This example demonstrates:

  • Header row with column titles
  • Editable number cells for budget and spent amounts
  • Automatic calculation of remaining amounts
  • Conditional styling (green for positive, red for negative)
  • Total row with sum calculations
  • Real-time updates when cells are edited

Common Issues / Troubleshooting

  1. Spreadsheet not displaying: Make sure you've imported the CSS file (@silevis/reactgrid/styles.css). The container div must have an explicit height.

  2. Cell editing not working: Ensure you're handling the onChanges event and updating your state properly. The changes handler receives an array of change objects.

  3. Data structure errors: Remember that ReactGrid expects a 2D array structure (rows of cells). Each cell must be an object with at least a type property.

  4. Style not applying: Cell styles should be included in the cell object's style property. Make sure you're spreading existing styles when updating cells.

  5. Performance with large datasets: For very large spreadsheets, consider implementing virtualization or pagination. ReactGrid handles moderate-sized datasets well, but extremely large grids may need optimization.

Next Steps

Now that you have a basic understanding of ReactGrid:

  • Learn about advanced cell types (date, checkbox, dropdown)
  • Explore formula support and calculations
  • Implement custom cell renderers
  • Add data validation and formatting
  • Learn about row and column operations
  • Check the official repository: https://github.com/silevis/reactgrid
  • Look for part 8 of this series for more advanced topics

Summary

You've successfully set up ReactGrid in your React application and created your first interactive spreadsheet. ReactGrid provides a straightforward way to build Excel-like interfaces with cell editing, calculations, and data manipulation, making it perfect for building data-intensive applications.

Top comments (0)