shadcn-ui / ui

Beautifully designed components that you can copy and paste into your apps. Accessible. Customizable. Open Source.
https://ui.shadcn.com
MIT License
73.41k stars 4.51k forks source link

[feat]: Lightweight Alternative to TanStack Table: Virtuoso #3296

Closed kevinschaich closed 4 months ago

kevinschaich commented 7 months ago

Feature description

I've been disappointed by the TanStack Table docs and similar to others (#2714, #1564, #1151), I've found it challenging to extend the basic example to have both virtualized rows and a sticky header row.

If anyone is in the same boat, I've found React Virtuoso to be wonderful. They have a dead simple table example with both virtualization and a sticky header in ~20 LOC.

This example can be extended to include shadcn/ui styling:

https://github.com/shadcn-ui/ui/assets/9244728/357663e4-92ec-4f6f-a445-248497032c5d

import { Table, TableCell, TableFooter, TableHead, TableHeader, TableRow } from '@/components/ui/table'
import { useMemo } from 'react'
import { TableVirtuoso } from 'react-virtuoso'

export const DataTable = () => {
    const users = useMemo(() => {
        return Array.from({ length: 1000 }, (_, index) => ({
            name: `User ${index}`,
            description: `Description for user ${index}`,
        }))
    }, [])

    return (
        <TableVirtuoso
            className='h-full w-full'
            data={users}
            components={{
                Table: (props) => <Table {...props} />,
                TableHead: (props) => <TableHeader {...props} className='bg-background' />,
                TableFoot: (props) => <TableFooter {...props} />,
                TableRow: (props) => <TableRow {...props} />,
            }}
            fixedHeaderContent={() => (
                <TableRow>
                    <TableHead>Name</TableHead>
                    <TableHead>Description</TableHead>
                </TableRow>
            )}
            itemContent={(index, user) => (
                <>
                    <TableCell>{user.name}</TableCell>
                    <TableCell>{user.description}</TableCell>
                </>
            )}
        />
    )
}

Note: you have to remove the wrapping div element for theTable component in components/ui/table.tsx or scrolling will not work.

You can also further extend it to support an arbitrary array of objects/records:

import { Table, TableFooter, TableHead, TableHeader, TableRow, TableCell } from '@/components/ui/table'
import { useMemo } from 'react'
import { TableVirtuoso } from 'react-virtuoso'

export const MyComponent = () => {
    const users = useMemo(() => {
        return Array.from({ length: 1000 }, (_, rowIndex) => ({
            name: `User ${rowIndex}`,
            description: `Description for user ${rowIndex}`,
            ...Array.from({ length: 10 }, (_, columnIndex) => `Column ${columnIndex} for user ${rowIndex}`),
        }))
    }, [])

    return <DataTable data={users} />
}

export const DataTable = ({ data }: { data: Record<string, any>[] }) => {
    if (!data || data.length === 0) {
        return null
    }

    const columns: string[] = Object.keys(data[0])

    return (
        <TableVirtuoso
            className='h-full w-full'
            data={data}
            components={{
                Table: (props) => <Table {...props} />,
                TableHead: (props) => <TableHeader {...props} className='bg-background' />,
                TableFoot: (props) => <TableFooter {...props} />,
                TableRow: (props) => <TableRow {...props} />,
            }}
            fixedHeaderContent={() => (
                <TableRow>
                    {columns.map((key) => (
                        <TableHead key={key}>{key}</TableHead>
                    ))}
                </TableRow>
            )}
            itemContent={(rowIndex, row) => (
                <>
                    {columns.map((column, columnIndex) => (
                        <TableCell key={`${rowIndex}-${columnIndex}`}>{row[column]}</TableCell>
                    ))}
                </>
            )}
        />
    )
}

This is a batteries-less-included approach: if you want filtering and sorting, you'll have to implement it yourself (this is better in my use case but I recognize it may not be for all).

Hope it may be helpful to others.

Affected component/components

Data Table, Table

Additional Context

Additional details here...

Before submitting

cmawhorter commented 6 months ago

i'm needing a data table and decided to skip tanstack/table after going through the open issues in that repo. many of them are significant and many relate to performance and things that shouldn't be problems for something that is v8.16.

however, the source for react-virtuoso is pretty basic and most of it relates to the basic html table elements. it makes me question whether data table should be included at all here. it feels like it's just one of those things that sucks and doesn't have a one-size-fits-all solution. like forms.

maybe data table is more of a blocks thing?

bzbetty commented 6 months ago

one of those things that sucks and doesn't have a one-size-fits-all solution. like forms.

to be fair I'm not sure forms are a one size fits all either, but I agree with the sentiment that maybe it should be more of a blocks/html only thing.

austinm911 commented 6 months ago

I think a data table should certainly be included here, it's a common B2B use case. But I have also struggled implementing data tables. I like how the tanstack docs include full working stackblitz/codesandbox projects. They are sometimes a bit overwhelming to go over, but perhaps the docs can be expanded in the Shad repo to extend more examples for the more complicated components like Data Tables.

I'll include some code I was playing around with some time ago that included column resizing and virtualization with a Shadcn Data Table, if anyone cares to look. Not going to claim it's great or works entirely though.

https://stackblitz.com/edit/tanstack-table-7djbwc?file=src/main.tsx

cmawhorter commented 6 months ago

to be fair I'm not sure forms are a one size fits all either

@bzbetty yes, that was what i was poorly trying to say. forms just suck and take a lot of effort

@austinm911 it should be mentioned that shadcn ui doesn't include a data table component. what it does is give some pointers and suggests tanstack/table. my argument is to remove that suggestion, because it would've saved me a day of research. ha.

as for the examples for tanstack -- while looking through the open issues, i found many people referencing those same examples as also being broken. so it just kinda seems like a mess of a component IMO

shadcn commented 4 months ago

This issue has been automatically closed because it received no activity for a while. If you think it was closed by accident, please leave a comment. Thank you.