Declarative React Table under 1kb

@saltyaom/react-table

Declarative React Table under 1kb.

Humanity Restored

Feature

  • No dependencies.
  • Light, 700 bytes on production.
  • Easy to understand, declarative.
  • Automatic key management.
  • Full control over table.
  • Full TypeScript support.

Size

Should be around 700 bytes, checkout Bundlephobia for accurate result.

Getting start

yarn add @saltyaom/react-table

// Or npm
npm install @saltyaom/react-table --save

Example

import Table from '@saltyaom/react-table'

const Example = () => {
    return (
        <Table
            header={['name', 'detail']}
            dataKey='name'
            data={[
                ['Fubuki', 'Waifriend'],
                ['Korone', 'Yubi yubi']   
            ]}
        />
    )
}

Why

Compose React table in a simple, elegant way.

Creating table in React is complicate.

Let create a simple table from the following data.

name type value
Okayu cat 1
Korone dog -1

Where the requirement is:

  • First field on table head is bold.
  • Value field must be color by the following:
    • if value >= 0, return green
    • otherwise, return red

Implement on normal React would be like:

const VTuberTable = () => {
    const header = ['name', 'description', 'value']
    const data = [
        ['Okayu' , 'cat', 10],
        ['Korone', 'dog', -1]
    ]

    return (
        <table>
            <thead className="head">
                <tr>
                    {header.map((title, index) => {
                        if(index === 0) return <th className="title bold">{title}</th>

                        return (
                            <th className="title">{title}</th>
                        )
                    })}
                </tr>
            </thead>
            <tbody className="body">
                {data.map(row => 
                    <tr key={row[0]}>
                        {row.map((data, index) => {
                            if(data === 2)
                                if(data >= 0)
                                    return (
                                <td 
                                    key={`${row[0]}-${index}-${data}`} 
                                    className="green"
                                >
                                    {data}
                                </td>
                            )
                                else
                                    return (
                                <td 
                                    key={`${row[0]}-${index}-${data}`} 
                                    className="red"
                                >
                                    {data}
                                </td>
                            )

                            return (
                                <td 
                                    key={`${row[0]}-${index}-${data}`} 
                                    className="black"
                                >
                                    {data}
                                </td>
                            )
                        })}
                    </tr>
                )}
            </tbody>
        </table>
    )
}

The problem is:

  • The code is very long.
  • Semantic table require a lot of boilerplate, thead, tr, table.
  • Hard to understand, imperative.
  • key managment is complex.

Entering @saltyaom/react-table

@saltyaom/react-table is a simple, declarative way to compose table in React.

All you need to do is specified your data and key, you can bring your className anywhere, even a custom condition for class.

In the other word, you have full control over the table even in a declarative way.

Let's re-implement previous table in @saltyaom/react-table.

import Table from '@saltyaom/react-table'

const VTuberTable = () => {
    const header = ['name', 'description', 'value']
    const data = [
        ['Okayu' , 'cat', 10],
        ['Korone', 'dog', -1]
    ]

    return (
        <Table 
            dataKey="name"
            header={header}
            data={data}

            theadClassName="head"
            tbodyClassName="body"

            // Apply 'title' to all <th> element
            allThClassName="title"
            // Apply 'bold' to index 0 <th>
            thClassName={['bold']}

            tdClassName={[
                '',
                '',
                // On index 2, apply custom condition
                (value: number) => value >= 0 ? 'green' : 'red'
            ]}
        />
    )
}

That's it, we have a simple happy ending for composing table in React.

Documentation

Table is the only export and is default export from @saltyaom/react-table.

The acceptable props is:

export interface ITable<
	T =
		| (string | number | JSX.Element)[]
		| readonly (string | number | JSX.Element)[]
> {
	/**
	 * Table header to be appear in `<thead>` in order.
	 *
	 * @example
	 * ['name', 'description']
	 */
	header: T

	/**
	 * Data to be appear for each row `<td>` in order.
	 *
	 * @example
	 * [
	 *  ['Korone', 'Dog'],
	 *  ['Okayu', 'Cat']
	 * ]
	 */
	data: T[] | readonly T[]

	/**
	 * Key of data, can be either `string` which match in `header` or number as index.
	 *
	 * @example
	 * 0
	 *
	 * @example
	 * 'name'
	 */
	dataKey?: string | number

	/**
	 * className of wrapper of `<table>`
	 * (element: `<section>`)
	 */
	wrapperClassName?: string
	/**
	 * className of `<table>`
	 */
	className?: string
	/**
	 * Width of each cell in order from left to right.
	 *
	 * @example
	 * [80, 160]
	 */
	cellsWidth?: number[]

	/**
	 * className of `<thead>`
	 */
	theadClassName?: string
	/**
	 * className of `<th>`
	 */
	thClassName?: string[]
	/**
	 * className to apply to all `<th>`
	 */
	allThClassName?: string

	/**
	 * className of `<tbody>`
	 */
	tbodyClassName?: string
	/**
	 * className of `<td>`
	 *
	 * Can be either `string` or `function()` which accepts `([valueof data:, index: number])`
	 *
	 * @example
	 * w-8
	 *
	 * @example
    // If nothing is returned, fallback to ''
	 * (rowData, index) => {
	 *  if(rowData.value === 0) return 'bg-blue-50'
	 *  if(rowData.index === 0) return 'bg-red-50'
	 * }
	 */
	trClassName?: string | ((data: readonly T[], index: number) => string)
	/**
	 * className of `<td>`
	 *
	 * Can be either `string[]` or `function()` which accepts `([valueof data:, index: number])`
	 *
	 * @example
	 * ['w-8', 'w-16']
	 *
	 * @example
    // If nothing is returned, fallback to ''
	 * (value, index) => {
	 *  if(value === 0) return 'text-red-500'
	 *  if(index === 0) return 'text-blue-500'
	 * }
	 */
	tdClassName?:
		| (string | ((data: any, index: number) => string))[]
		| ((data: string, index: number) => string)
	/**
	 * className to apply to all `<tbody>`
	 */
	allTdClassName?: string

	/**
	 * Prepend element before table
	 *
	 * @example
	 * <nav>
	 * 	 <input
	 * 	   name="search"
	 * 	   type="text"
	 *     placeholder="Search"
	 *     onChange={handleSearch}
	 *   />
	 * </nav>
	 */
	beforeTable?: JSX.Element
	/**
	 * Append element after table
	 *
	 * @example
	 * <section className="pagination">
	 * 	<button className="prev" onClick={previous}>Previous</button>
	 * 	<button className="next" onClick={next}>Next</button>
	 * </section>
	 */
	afterTable?: JSX.Element

	/**
	 * Add custom props to `<table>` element
	 *
	 * @example
	 * {
	 *   style={
	 *     borderCollapse: 'collapse'
	 *   }
	 * }
	 */
	tableProps?: Omit<
		DetailedHTMLProps<
			TableHTMLAttributes<HTMLTableElement>,
			HTMLTableElement
		>,
		'className'
	>

	/**
	 * Add custom props to `<thead>` element
	 *
	 * @example
	 * {
	 *   onClick: () => console.log("Clicked")
	 * }
	 */
	theadProps?: Omit<
		DetailedHTMLProps<
			HTMLAttributes<HTMLTableSectionElement>,
			HTMLTableSectionElement
		>,
		'className'
	>
	/**
	 * Add custom props to `<th>` element
	 *
	 * @example
	 * (data, index) => {
	 *   if(isOdd(index)) return ({ className: '--odd' })
	 * }
	 */
	thProps?: (
		data: T[keyof T],
		index: number
	) => Omit<
		DetailedHTMLProps<
			ThHTMLAttributes<HTMLTableHeaderCellElement>,
			HTMLTableHeaderCellElement
		>,
		'className'
	> | void
	/**
	 * Add custom props to `<tbody>` element
	 *
	 * @example
	 * {
	 *   onClick: (data, index) => console.log("Clicked")
	 * }
	 */
	tbodyProps?: Omit<
		DetailedHTMLProps<
			HTMLAttributes<HTMLTableSectionElement>,
			HTMLTableSectionElement
		>,
		'className'
	>
	/**
	 * Add custom props to `<tr>` element
	 *
	 * @example
	 * (data, index) => {
	 *   if(isOdd(index)) return ({ className: '--odd' })
	 * }
	 */
	trProps?: (
		row: T,
		index: number
	) => Omit<
		DetailedHTMLProps<
			HTMLAttributes<HTMLTableRowElement>,
			HTMLTableRowElement
		>,
		'className'
	> | void
	/**
	 * Add custom props to `<td>` element
	 *
	 * @example
	 * (data, { column, row }) => ({
	 *   onClick: () => console.log(column, row)
	 * })
	 */
	tdProps?: (
		data: T[keyof T],
		indexes: {
			column: number
			row: number
		}
	) => Omit<
		DetailedHTMLProps<
			TdHTMLAttributes<HTMLTableDataCellElement>,
			HTMLTableDataCellElement
		>,
		'className'
	> | void
}

For more information, you can looks directly in the source code as it's very easy to read.

GitHub

https://github.com/SaltyAom/react-table