Nested Tables in React: Beyond Row Grouping with Independent Grid Structures

Nested TablesAdvanced TutorialHierarchical Data

Discover how nested tables revolutionize hierarchical data display by allowing each level to have completely different columns. Learn the architecture behind this powerful feature and when to use it over traditional row grouping.

You're building a corporate dashboard. The top level shows companies with 9 columns—name, industry, CEO, revenue, market cap, and more. When users expand a company, they see divisions with 6 columns—division ID, name, revenue, profit margin, headcount, and location. Expand a division, and you get teams with 19 detailed columns—manager, budget, headcount, skills, certifications, and everything else.

This isn't traditional row grouping where all levels share the same columns. This is nested tables—a more powerful pattern where each hierarchical level gets its own completely independent table with different columns, configuration, and features.

In this deep dive, we'll explore how nested tables work under the hood, the technical challenges they solve, and why implementing them correctly requires sophisticated architecture that most developers never see.

Interactive Demo: Corporate Hierarchy

Try expanding companies to see divisions with completely different columns. This interactive demo shows how nested tables allow each level to display exactly the data it needs. For complete implementation details and advanced features, check out the nested tables documentation.

Row Grouping vs Nested Tables: Understanding the Difference

Before diving into nested tables, it's crucial to understand how they differ from traditional row grouping:

Row Grouping

  • • All levels share the same column structure
  • • Child rows simply indent under parents
  • • Same headers visible at every depth
  • • Simpler implementation
  • • Best for homogeneous data

Nested Tables

  • • Each level has independent columns
  • • Full table rendered inside parent row
  • • Different headers at each depth
  • • Complex architecture required
  • • Best for heterogeneous hierarchies

The Key Insight

Nested tables render complete, independent SimpleTable instances inside parent rows. Each nested table is a full-featured grid with its own headers, sorting, filtering, pagination, and all other features—completely separate from its parent.

Real-World Example: Corporate Hierarchy

Let's look at a concrete example that demonstrates why nested tables are necessary. This three-level hierarchy shows companies, divisions, and teams—each with dramatically different data needs:

Level 0: Companies (9 columns)

High-level overview: industry, headquarters, market cap, CEO, revenue, employees

React TSX
1const companyHeaders: HeaderObject[] = [
2 {
3 accessor: "companyName",
4 label: "Company",
5 width: 200,
6 expandable: true,
7 nestedTable: {
8 defaultHeaders: divisionHeaders, // Different columns!
9 autoExpandColumns: true,
10 enableRowSelection: true,
11 },
12 },
13 { accessor: "industry", label: "Industry", width: 150 },
14 { accessor: "founded", label: "Founded", width: 100 },
15 { accessor: "headquarters", label: "HQ", width: 180 },
16 { accessor: "stockSymbol", label: "Symbol", width: 100 },
17 { accessor: "marketCap", label: "Market Cap", width: 120 },
18 { accessor: "ceo", label: "CEO", width: 150 },
19 { accessor: "revenue", label: "Revenue", width: 120 },
20 { accessor: "employees", label: "Employees", width: 120 },
21];

Level 1: Divisions (6 columns)

Division-level details with financial metrics and operational data

React TSX
1const divisionHeaders: HeaderObject[] = [
2 { accessor: "divisionId", label: "Division ID", width: 120 },
3 { accessor: "divisionName", label: "Division", width: 200 },
4 { accessor: "revenue", label: "Revenue", width: 120 },
5 { accessor: "profitMargin", label: "Profit Margin", width: 130 },
6 { accessor: "headcount", label: "Headcount", width: 110, type: "number" },
7 { accessor: "location", label: "Location", width: 150 },
8];

Level 2: Teams (19 columns)

Detailed operational data: manager, budget, headcount, skills, certifications, and more

React TSX
1const teamHeaders: HeaderObject[] = [
2 { accessor: "teamName", label: "Team", width: 200 },
3 { accessor: "manager", label: "Manager", width: 150 },
4 { accessor: "location", label: "Location", width: 130 },
5 { accessor: "budget", label: "Budget", width: 100 },
6 { accessor: "headcount", label: "Headcount", width: 110 },
7 { accessor: "projects", label: "Projects", width: 100 },
8 { accessor: "efficiency", label: "Efficiency", width: 100 },
9 { accessor: "satisfaction", label: "Satisfaction", width: 110 },
10 { accessor: "turnover", label: "Turnover", width: 100 },
11 { accessor: "avgSalary", label: "Avg Salary", width: 110 },
12 { accessor: "topSkill", label: "Top Skill", width: 150 },
13 { accessor: "certifications", label: "Certs", width: 80 },
14 { accessor: "remoteWorkers", label: "Remote", width: 90 },
15 { accessor: "officeSpace", label: "Office Space", width: 120 },
16 { accessor: "equipment", label: "Equipment", width: 110 },
17 { accessor: "trainingHours", label: "Training Hrs", width: 110 },
18 { accessor: "innovations", label: "Innovations", width: 110 },
19 { accessor: "patents", label: "Patents", width: 90 },
20 { accessor: "awards", label: "Awards", width: 180 },
21];

Why This Matters

With row grouping, you'd be forced to show all 34 columns at every level (9 + 6 + 19), or hide columns conditionally (messy). With nested tables, each level shows exactly the data it needs—no more, no less. The parent shows strategic overview, children show tactical details.

How Nested Tables Work: The Architecture

Implementing nested tables is deceptively complex. Here's what happens under the hood when you configure nested tables in Simple Table:

1. Configuration & Setup

Nested tables are configured via the nestedTable property on a HeaderObject that has expandable: true:

React TSX
1{
2 accessor: "companyName",
3 label: "Company",
4 expandable: true,
5 nestedTable: {
6 defaultHeaders: divisionHeaders, // Column structure for nested level
7 autoExpandColumns: true, // Any SimpleTableProps work here
8 enableRowSelection: true,
9 // ... any other SimpleTableProps except 'rows'
10 }
11}

The nestedTable object accepts all SimpleTableProps except rows (which come from parent data). This means nested tables can have their own sorting, filtering, pagination, themes, and any other feature.

2. Data Flow & Hierarchy

You still use the rowGrouping prop to define the hierarchy path:

React TSX
1<SimpleTable
2 defaultHeaders={companyHeaders}
3 rows={data}
4 rowGrouping={["divisions", "teams"]} // Hierarchy: companies → divisions → teams
5 expandAll={false}
6/>

When the library processes this:

  • It checks if the expandable header has a nestedTable configuration
  • If yes, it creates a special "nested grid row" instead of rendering individual child rows
  • The nested grid row renders a full SimpleTable component with child data from the parent row's divisions property
  • If that nested table also has nestedTable config, the process repeats recursively

3. Row Flattening & Virtualization

For virtualization to work, the library must flatten the hierarchical data into a flat array. Here's where it gets interesting:

React TSX
1// Flattened array for virtualization
2[
3 { type: "data", depth: 0, row: companyRow1, id: "0" },
4 { type: "nested-table", depth: 1, parentRow: companyRow1, nestedConfig: {...}, id: "0-divisions" },
5 { type: "data", depth: 0, row: companyRow2, id: "1" },
6 // ... more rows
7]

When a row with nestedTable config is expanded, the library injects a special row with type: "nested-table". This row:

  • • Spans the full width of the parent table
  • • Contains all information needed to render the nested SimpleTable
  • • Has a calculated height based on child row count
  • • Is treated as a single row by the virtualization system

4. Dynamic Height Calculation

One of the trickiest parts: calculating the exact height of nested tables for virtualization. The formula is:

React TSX
1// Height calculation for nested table
2const nestedTableHeight =
3 headerHeight + headerBorder +
4 (childRowCount × (rowHeight + rowSeparator)) +
5 topPadding + bottomPadding + tableBorder;
6
7// Respect maximum height constraint
8const finalHeight = Math.min(nestedTableHeight, maxNestedTableHeight);

This calculation must be pixel-perfect. If it's off by even 1px, virtualization breaks—rows appear in wrong positions, scrolling stutters, and the entire table becomes unusable.

5. Position Tracking & Stable IDs

Each row needs a unique, stable ID for React reconciliation and state management:

React TSX
1// Row ID format: "parentIndex-groupingKey-childIndex-groupingKey-grandchildIndex"
2"0" // Company at index 0
3"0-divisions-2" // Division at index 2 in company 0
4"0-divisions-2-teams-1" // Team at index 1 in division 2 of company 0

These IDs allow the library to:

  • • Track expansion state across re-renders
  • • Handle programmatic expand/collapse
  • • Maintain selection state at any nesting level
  • • Support animations and transitions

The Hidden Complexities

Nested tables look simple from the API perspective, but implementing them correctly requires solving numerous challenging problems:

1. Independent Grid State Management

Each nested table is a complete SimpleTable instance with its own sorting, filtering, pagination, and selection state. Managing state across multiple independent tables while keeping them synchronized with parent data is complex. Changes to parent data must cascade to nested tables without losing their internal state.

2. Virtualization Integration

Nested tables must integrate seamlessly with the parent's virtualization system. Each nested table occupies a single position in the flattened array, but its height varies based on child count. Height calculations must be exact, and transform positioning must account for variable-height nested tables above the current scroll position.

3. Theme & Customization Inheritance

Child tables inherit parent's customTheme by default, but can override specific properties. The merge strategy must be smart: { ...parentTheme, ...childTheme }. This cascades through multiple levels, so a grandchild table might inherit from parent, override some properties, and add its own.

4. Recursive Row Grouping

When nesting multiple levels, each nested table receives the remaining rowGrouping array. If parent has ["divisions", "teams"], first nested level gets ["teams"]. This enables automatic multi-level nesting without manual configuration at each level.

5. Expansion State Tracking

The library must track which rows are expanded at each depth level. Uses expandedDepths Set for default expansion by depth, plus expandedRows and collapsedRows Maps for manual overrides. Row IDs must be stable across data updates to maintain expansion state.

6. Selection & Interaction Independence

Each nested table can have independent row selection. Parent and child selections are managed separately. Callbacks receive context about which table level triggered the event. This allows selecting a company without selecting its divisions, or vice versa.

7. Styling & Layout Coordination

Nested tables span full parent width (grid-column: 1 / -1). Padding and borders create visual separation. Must handle pinned columns, scrolling, and responsive behavior. Column borders, hover effects, and themes cascade to nested tables while maintaining visual hierarchy.

8. Performance at Scale

Each nested table is a full React component tree. Virtualization only renders visible rows, including nested tables. Collapsed nested tables are not rendered (zero performance cost). However, large datasets with many expanded nested tables can impact performance—each visible nested table adds dozens of DOM nodes and React components.

9. Data Access Patterns

Uses dot notation and array bracket notation for nested data access. Examples: "divisions", "divisions[0].name", "latest.score". Recursive traversal to extract child data from parent rows. Must handle missing data gracefully (undefined divisions array, null values, etc.).

10. Memory Management

Nested tables increase memory usage when many are expanded. Each nested table maintains its own state, event handlers, and React fiber tree. Proper cleanup on collapse is essential. Memoization and React.memo help, but can't eliminate the fundamental cost of rendering multiple independent tables.

Implementation: Building Nested Tables

Despite the complex architecture, using nested tables in Simple Table is straightforward. Here's a complete implementation:

Step 1: Structure Your Data

React TSX
1const data = [
2 {
3 // Company level (Level 0)
4 companyName: "TechCorp Global",
5 industry: "Technology",
6 founded: 2015,
7 headquarters: "San Francisco, CA",
8 stockSymbol: "TECH",
9 marketCap: "$150B",
10 ceo: "Jane Smith",
11 revenue: "$45B",
12 employees: 50000,
13
14 // Child divisions (Level 1)
15 divisions: [
16 {
17 divisionName: "Cloud Services",
18 revenue: "$15B",
19 profitMargin: "35%",
20
21 // Child teams (Level 2)
22 teams: [
23 {
24 teamName: "Infrastructure",
25 manager: "John Doe",
26 location: "Seattle",
27 budget: "$2.8M",
28 headcount: 28,
29 projects: 12,
30 efficiency: "92%",
31 satisfaction: 4.5,
32 // ... 11 more team fields
33 },
34 // ... more teams
35 ]
36 },
37 // ... more divisions
38 ]
39 },
40 // ... more companies
41];

Step 2: Define Headers for Each Level

React TSX
1// Level 2: Team headers (innermost level)
2const teamHeaders: HeaderObject[] = [
3 { accessor: "teamName", label: "Team", width: 200 },
4 { accessor: "manager", label: "Manager", width: 150 },
5 { accessor: "budget", label: "Budget", width: 100 },
6 // ... 16 more columns
7];
8
9// Level 1: Division headers (middle level)
10const divisionHeaders: HeaderObject[] = [
11 {
12 accessor: "divisionName",
13 label: "Division",
14 width: 250,
15 expandable: true,
16 nestedTable: {
17 defaultHeaders: teamHeaders, // Point to next level
18 autoExpandColumns: true,
19 enableRowSelection: true,
20 },
21 },
22 { accessor: "revenue", label: "Revenue", width: 150 },
23 { accessor: "profitMargin", label: "Profit Margin", width: 150 },
24];
25
26// Level 0: Company headers (top level)
27const companyHeaders: HeaderObject[] = [
28 {
29 accessor: "companyName",
30 label: "Company",
31 width: 200,
32 expandable: true,
33 nestedTable: {
34 defaultHeaders: divisionHeaders, // Point to next level
35 autoExpandColumns: true,
36 enableRowSelection: true,
37 },
38 },
39 { accessor: "industry", label: "Industry", width: 150 },
40 { accessor: "founded", label: "Founded", width: 100 },
41 // ... 6 more columns
42];

Step 3: Configure the Table

React TSX
1import { SimpleTable } from "simple-table-core";
2import "simple-table-core/styles.css";
3
4function CorporateHierarchy() {
5 return (
6 <SimpleTable
7 defaultHeaders={companyHeaders}
8 rows={data}
9
10 // Define the hierarchy path
11 rowGrouping={["divisions", "teams"]}
12
13 // Start collapsed for better performance
14 expandAll={false}
15
16 // Enable features
17 columnResizing={true}
18 enableRowSelection={true}
19
20 // Set height
21 height="600px"
22
23 // Optional: Handle expansion events
24 onRowGroupExpand={({ row, depth, groupingKey, isExpanded }) => {
25 console.log(`Expanded ${groupingKey} at depth ${depth}`, row);
26 }}
27 />
28 );
29}

That's It!

The library handles all the complexity: row flattening, height calculations, virtualization integration, state management, theme inheritance, and recursive nesting. You just define the structure and configuration.

When to Use Nested Tables

Nested tables are powerful but not always necessary. Here's when to use them versus traditional row grouping:

Use Nested Tables When:

  • • Each hierarchy level needs different columns
  • • Parent shows overview, children show details (different granularity)
  • • Child levels need independent features (sorting, filtering, pagination)
  • • Data structure is heterogeneous (companies → divisions → teams)
  • • You want visual separation between levels

Use Row Grouping When:

  • • All levels share the same columns
  • • Simple indentation is sufficient (visual hierarchy only)
  • • Data structure is homogeneous (folders → subfolders → files)
  • • You need aggregate functions (sum, count, avg) across levels
  • • Performance is critical (lighter weight)

Real-World Use Cases

Corporate Hierarchy Dashboard

Companies → Divisions → Teams: Show company overview (9 columns), division metrics (6 columns), and detailed team information (19 columns). Each level has completely different data needs.

Perfect for executive dashboards, organizational analytics, and strategic planning tools.

E-commerce Order Management

Orders → Line Items → Shipments: Display order summary (customer, date, total), item details (product, quantity, price), and tracking information (carrier, tracking number, status) with different columns at each level.

Ideal for admin panels, customer service tools, and fulfillment systems.

Project Management System

Projects → Milestones → Tasks: Show project overview (name, status, budget, timeline), milestone progress (name, completion, deadline), and task details (assignee, priority, time tracking, dependencies).

Great for project management tools, agile boards, and resource planning apps.

Financial Reporting

Accounts → Sub-accounts → Transactions: Present account summaries (type, balance, owner), sub-account details (name, balance, category), and itemized transactions (date, description, amount, status).

Perfect for accounting software, banking apps, and financial dashboards.

Performance Considerations

Nested tables are more resource-intensive than row grouping. Here's how to optimize performance:

✅ Start with expandAll=

Don't render all nested tables on initial load. Let users expand what they need. This dramatically reduces initial render time and memory usage. Collapsed nested tables have zero performance cost—they're not rendered at all.

✅ Virtualization Works Automatically

Simple Table's virtualization handles nested tables seamlessly. Even if you have 100 expanded nested tables, only the visible ones are rendered. Scrolling remains smooth because DOM nodes are recycled.

✅ Use Pagination for Deep Hierarchies

If you have 1000+ top-level rows, enable pagination. This limits the number of rows that can be expanded simultaneously, keeping memory usage bounded. Combine with virtualization for optimal performance.

✅ Memoize Data Transformations

If you transform data before passing to the table, wrap it in useMemo. Don't recalculate the entire hierarchy on every render. This prevents unnecessary re-renders of nested tables.

❌ Avoid: Expanding Everything

Don't use expandAll= with large datasets. If you have 100 companies with 5 divisions each, that's 500 nested tables rendered simultaneously. Memory usage explodes and performance tanks.

❌ Avoid: Complex Cell Renderers in Nested Tables

Each nested table multiplies the cost of cell renderers. If your nested table has 50 rows with complex custom renderers, and you have 10 expanded nested tables, that's 500 complex components rendered. Keep nested table cell renderers simple.

The Power of Independent Grid Structures

Nested tables represent a fundamental shift in how we think about hierarchical data display. Instead of forcing all levels to share the same column structure, they embrace the reality that different levels of a hierarchy often need completely different views of the data.

The architecture required to implement nested tables correctly is sophisticated: recursive row flattening, dynamic height calculation, virtualization integration, independent state management, and theme inheritance. But when done right, the API becomes simple—just add a nestedTable property to your expandable column.

Key takeaways:

  • Use nested tables when each level needs different columns—not when all levels share the same structure
  • Each nested table is a full SimpleTable instance with independent features, state, and configuration
  • The architecture is complex but the API is simple—configure once and the library handles the rest
  • Start with expandAll= for better performance, let users expand what they need
  • Virtualization handles nested tables automatically—only visible nested tables are rendered

Whether you're building corporate dashboards, e-commerce admin panels, project management tools, or financial reporting systems, nested tables provide the flexibility to display hierarchical data exactly how your users need to see it—with different columns, features, and configurations at each level of the hierarchy.

Ready to implement nested tables?

Simple Table makes complex hierarchical data display simple with nested tables that support independent grid structures at each level. No complex configuration required.