diff --git a/plans b/plans new file mode 160000 index 00000000..cd88943c --- /dev/null +++ b/plans @@ -0,0 +1 @@ +Subproject commit cd88943c5efddb78939acccca79f240c5705b698 diff --git a/www/content/blog/2026/07/10/building-pivoted-react-datagrids-with-generated-columns.page.md b/www/content/blog/2026/07/10/building-pivoted-react-datagrids-with-generated-columns.page.md new file mode 100644 index 00000000..0628ed11 --- /dev/null +++ b/www/content/blog/2026/07/10/building-pivoted-react-datagrids-with-generated-columns.page.md @@ -0,0 +1,163 @@ +--- +title: Building pivoted React DataGrids with generated columns +description: Turn grouped data into cross-tab reports with Infinite Table pivoting, generated columns, custom pivot headers, and total columns. +date: 2026-07-10 +author: radu +tags: pivoting, grouping, analytics +--- + +Pivot tables are one of those features that start as a reporting request and quickly become a UI architecture problem. + +You need to group rows, aggregate values, create columns from data values, keep those columns typed, and still leave enough room to customize headers, widths, formatting, totals, and server-side loading. Infinite Table's pivoting feature keeps that work inside the `DataSource` pipeline so your React grid can render a report-style layout without hardcoding every possible result column. + +The docs cover this in the [pivoting guide](/docs/learn/grouping-and-pivoting/pivoting/overview). This article walks through the key idea: define pivoting at the data level, then render the generated columns in ``. + +## Pivoting belongs in the DataSource + +In Infinite Table, the `DataSource` owns grouping, pivoting, and aggregation because those features all reshape the data before the grid renders it. + +```tsx +const groupBy = [{ field: 'department' }, { field: 'country' }]; +const pivotBy = [{ field: 'team' }]; + + + groupBy={groupBy} + pivotBy={pivotBy} + aggregationReducers={aggregationReducers} +> + {({ pivotColumns, pivotColumnGroups }) => { + return ( + + columns={columns} + pivotColumns={pivotColumns} + pivotColumnGroups={pivotColumnGroups} + /> + ); + }} +; +``` + +The `children` render prop of the `DataSource` is an important handoff in the Pivot DataGrid. The `DataSource` looks at the configured and , does all the data grouping and computations and then gives the table the generated `pivotColumns` and `pivotColumnGroups`. + +Without that split, you would have to scan the dataset yourself, discover every pivot value, construct matching columns, wire column groups, and keep the result in sync as grouping or pivoting changes. + +## A practical example: grouping by role, pivoting by geography + +Imagine a developers analytics grid where rows are grouped by `preferredLanguage` and `stack`, then pivoted by `country` and whether a developer can design. Instead of showing one salary column, the grid can show aggregated salary columns for each country/design combination. + +The docs example below does exactly that. It groups developers, pivots the salary aggregation into generated columns, and starts with all groups collapsed so the cross-tab shape is easy to scan. + + + + + +You can find this demo in our [pivoting docs](/docs/learn/grouping-and-pivoting/pivoting/overview). Expand a group to see the aggregated salary values distributed across generated pivot columns. + + + +```tsx live file="$DOCS/learn/grouping-and-pivoting/pivoting/pivoting-example.page.tsx" + +``` + + + +## Generated does not mean generic + +Generated columns still need to feel like part of your app. Infinite Table gives you a few places to customize them. + +The first option is inheritance. If an aggregation reducer is bound to a field that already has a column, the generated pivot column inherits the original column configuration. + +```tsx +const columns: InfiniteTablePropColumns = { + salary: { + field: 'salary', + type: 'number', + style: { color: 'red' }, + } +} + +const aggregationReducers = { + avgSalary: { + field: 'salary', + reducer: 'avg', + } +} + +``` + +That means your formatting, sizing, and column behavior can stay close to the original field definition. + +When you need more control, use . It can be an object applied to all generated columns for that pivot level, or a function that receives the generated column metadata. + +```tsx +const pivotBy: DataSourcePivotBy[] = [ + { field: 'country' }, + { + field: 'canDesign', + column: ({ column }) => { + const lastKey = column.pivotGroupKeys[column.pivotGroupKeys.length - 1]; + + return { + header: lastKey === 'yes' ? 'Designer' : 'Non-designer', + }; + }, + }, +]; +``` + +That small callback is enough to turn raw data values into business-friendly column headers while keeping the column generation automatic. + +## Totals and grand totals + +Pivoted reports usually need totals. Infinite Table supports both: + +- for totals inside pivot groups +- for grand-total columns across the configured aggregations + +```tsx + + columns={columns} + pivotColumns={pivotColumns} + pivotColumnGroups={pivotColumnGroups} + pivotTotalColumnPosition="end" + pivotGrandTotalColumnPosition="start" +/> +``` + +This gives you the spreadsheet-like summary columns people expect from a pivot view, while keeping the placement explicit. + + + + + +This docs example places regular pivot totals at the end of each pivot group and grand-total columns at the start of the grid. + + + +```tsx live file="$DOCS/reference/pivot-grand-total-column-position-example.page.tsx" + +``` + + + +## When pivoting is a good fit + +Reach for pivoting when the question is not "which rows match this filter?" but "how do values compare across dimensions?" + +Good use cases include: + +- revenue by region and product line +- headcount by department and location +- average salary by technology stack and country +- support volume by priority and channel +- inventory by warehouse and status + +In each case, the column structure depends on the data. That is where generated pivot columns help: you describe the dimensions and aggregations, and Infinite Table creates the report surface. + +## Go deeper in the docs + +Start with the [pivoting overview](/docs/learn/grouping-and-pivoting/pivoting/overview) for the core `pivotBy` setup. + +Then read [customizing pivot columns](/docs/learn/grouping-and-pivoting/pivoting/customizing-pivot-columns) to tune generated headers, widths, inherited column behavior, and per-pivot-value column configuration. + +If your data is too large to reshape in the browser, the same pivoting guide also covers server-side pivoting, where `DataSource.data` returns already-pivoted groups while Infinite Table keeps the UI model consistent. diff --git a/www/src/components/CodeBlock/index.tsx b/www/src/components/CodeBlock/index.tsx index 4109af22..1415b554 100644 --- a/www/src/components/CodeBlock/index.tsx +++ b/www/src/components/CodeBlock/index.tsx @@ -8,7 +8,7 @@ import { ClipboardCopy } from './ClipboardCopy'; type BasicCodeBlockProps = { className?: string; inline?: boolean; - highlightLines?: string; + highlightLines?: number[]; title?: string; children?: string; lang?: string; @@ -44,7 +44,10 @@ function CodeBlockCmp(props: Omit) {
<> {/* @ts-ignore */} - + {props.children} diff --git a/www/src/components/CodeSnippet/index.tsx b/www/src/components/CodeSnippet/index.tsx index 1695a3b1..f086cc1d 100644 --- a/www/src/components/CodeSnippet/index.tsx +++ b/www/src/components/CodeSnippet/index.tsx @@ -14,7 +14,7 @@ export type CodeSnippetProps = { file?: string; lang?: string; files: SandpackInputFile[]; - highlightLines?: string; + highlightLines?: number[]; importedPackages?: string[]; viewMode?: 'code' | 'preview' | 'both'; cwd?: string; @@ -63,7 +63,11 @@ export function CodeSnippet(props: CodeSnippetProps) { } return ( - + {files[0]?.code} ); diff --git a/www/src/components/SyntaxHighlight/SyntaxHighlight.tsx b/www/src/components/SyntaxHighlight/SyntaxHighlight.tsx index babe6e83..481f6804 100644 --- a/www/src/components/SyntaxHighlight/SyntaxHighlight.tsx +++ b/www/src/components/SyntaxHighlight/SyntaxHighlight.tsx @@ -28,11 +28,11 @@ function Mermaid({ chart }: { chart: string }) { } interface SyntaxHighlightProps { - file: string; - title: string; + file?: string; + title?: string; children: string; className?: string; - highlightLines: number[]; + highlightLines?: number[]; noMargin?: boolean; noMarkers?: boolean; }