Skip to content

Instantly share code, notes, and snippets.

@yarinsa
Created July 20, 2025 09:40
Show Gist options
  • Save yarinsa/f49c50329175f0f469d996558cc1e619 to your computer and use it in GitHub Desktop.
Save yarinsa/f49c50329175f0f469d996558cc1e619 to your computer and use it in GitHub Desktop.
react-table builder
// table-builder.ts - Functional Programming with Fluent Interface
import { type ColumnDef } from "@tanstack/react-table"
import {
createDragDropPlugin,
createSelectionPlugin,
createFilteringPlugin,
createGroupingPlugin,
addDragHandleColumn,
addSelectionColumn,
enableColumnGrouping,
type TablePlugin
} from "./table-plugins"
interface TableConfig<TData> {
columns: ColumnDef<TData>[]
plugins: TablePlugin<TData>[]
}
// This is where the magic happens - pure transformation functions
// Each function takes a config and returns a new config with added functionality
type TableTransform<TData> = (config: TableConfig<TData>) => TableConfig<TData>
// Selection plugin transformer - adds checkbox column + bulk actions
const withSelection = <TData>(config: {
enableBulkActions?: boolean
bulkActions?: React.ComponentType<{ selectedRows: TData[]; table: any }>
} = {}): TableTransform<TData> =>
({ columns, plugins }) => ({
columns: addSelectionColumn(columns), // Inject checkbox column
plugins: [...plugins, createSelectionPlugin(config)] // Add selection behavior
})
// Drag & drop transformer - adds grip handle + reorder functionality
const withDragDrop = <TData>(config: {
onReorder?: (data: TData[]) => void
getRowId?: (row: TData) => string | number
}): TableTransform<TData> =>
({ columns, plugins }) => ({
columns: addDragHandleColumn(columns), // Inject drag handle column
plugins: [...plugins, createDragDropPlugin(config)] // Add drag behavior
})
// Filtering transformer - no column changes, just adds search/filter behavior
const withFiltering = <TData>(config: {
globalSearch?: boolean
globalSearchPlaceholder?: string
columnFilters?: Record<string, any>
} = {}): TableTransform<TData> =>
({ columns, plugins }) => ({
columns, // No column changes needed
plugins: [...plugins, createFilteringPlugin(config)]
})
// Grouping transformer - marks columns as groupable + adds grouping behavior
const withGrouping = <TData>(config: {
groupableColumns: string[]
groupingLabels?: Record<string, string>
onGroupClick?: (groupValue: string, groupData: TData[]) => void
}): TableTransform<TData> =>
({ columns, plugins }) => ({
columns: enableColumnGrouping(columns, config.groupableColumns), // Mark columns as groupable
plugins: [...plugins, createGroupingPlugin(config)]
})
// Classic functional composition - this is what makes it all work together
const pipe = <T>(...fns: Array<(arg: T) => T>) => (value: T): T =>
fns.reduce((acc, fn) => fn(acc), value)
// The fluent interface that developers actually use
// Internally collects pure transformations, applies them all at once in build()
class TableBuilder<TData> {
private transforms: TableTransform<TData>[] = []
constructor(private initialColumns: ColumnDef<TData>[]) {}
// Each method just collects the transformation, doesn't execute immediately
addSelection(config: Parameters<typeof withSelection<TData>>[0] = {}) {
this.transforms.push(withSelection(config))
return this // Fluent interface
}
addDragDrop(config: Parameters<typeof withDragDrop<TData>>[0]) {
this.transforms.push(withDragDrop(config))
return this
}
addFiltering(config: Parameters<typeof withFiltering<TData>>[0] = {}) {
this.transforms.push(withFiltering(config))
return this
}
addGrouping(config: Parameters<typeof withGrouping<TData>>[0]) {
this.transforms.push(withGrouping(config))
return this
}
// This is where all transformations get applied via functional composition
build(): TableConfig<TData> {
return pipe(...this.transforms)({
columns: this.initialColumns,
plugins: []
})
}
}
// Simple factory function - keeps the interface clean
export const createTableBuilder = <TData>(columns: ColumnDef<TData>[]) =>
new TableBuilder(columns)
// Usage Examples - How I actually use this in production:
// The API that finally makes sense - clean, readable, and composable
const { columns, plugins } = createTableBuilder(baseColumns)
.addSelection({ enableBulkActions: true })
.addDragDrop({ onReorder: setData })
.addFiltering({ globalSearch: true })
.addGrouping({ groupableColumns: ["role"] })
.build()
// Conditional features work beautifully - no more prop drilling hell
const builder = createTableBuilder(baseColumns)
.addSelection({ enableBulkActions: true })
.addFiltering({ globalSearch: true })
// Only add drag & drop if user has permissions
if (userCanReorder) {
builder.addDragDrop({ onReorder: setData })
}
const { columns, plugins } = builder.build()
// Drop it into the table component - that's it!
// <DataTable columns={columns} data={data} plugins={plugins} />
// Pro tip: You can even create reusable builder configurations
const createAdminTable = (baseColumns) =>
createTableBuilder(baseColumns)
.addSelection({ enableBulkActions: true })
.addDragDrop({ onReorder: handleReorder })
.addFiltering({ globalSearch: true })
const createReadOnlyTable = (baseColumns) =>
createTableBuilder(baseColumns)
.addFiltering({ globalSearch: true })
.addGrouping({ groupableColumns: ["status", "category"] })
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment