Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions docs/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -822,8 +822,10 @@
{
"label": "solid",
"children": [
{ "to": "framework/solid/examples/basic", "label": "Basic" },
{ "to": "framework/solid/examples/basic-table-helper", "label": "Basic with Helpers" },
{ "to": "framework/solid/examples/basic-use-table", "label": "Basic (createTable)" },
{ "to": "framework/solid/examples/basic-app-table", "label": "Basic (createAppTable)" },
{ "to": "framework/solid/examples/basic-external-state", "label": "Basic (External State)" },
{ "to": "framework/solid/examples/basic-external-store", "label": "Basic (External Store)" },
{ "to": "framework/solid/examples/column-groups", "label": "Header Groups" }
]
},
Expand Down Expand Up @@ -999,8 +1001,9 @@
{
"label": "solid",
"children": [
{ "to": "framework/solid/examples/bootstrap", "label": "Solid Bootstrap" }
]
{ "to": "framework/solid/examples/bootstrap", "label": "Solid Bootstrap" },
{ "to": "framework/solid/examples/composable-tables", "label": "Composable Tables" },
{ "to": "framework/solid/examples/with-tanstack-query", "label": "With TanStack Query" }]
},
{
"label": "vue",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "tanstack-table-example-solid-basic",
"name": "tanstack-table-example-solid-basic-app-table",
"version": "0.0.0",
"description": "",
"scripts": {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createTableHelper, flexRender } from '@tanstack/solid-table'
import { createTableHook } from '@tanstack/solid-table'
import { For, createSignal } from 'solid-js'

// This example uses the new `createTableHelper` method to create a re-usable table helper object instead of independently using the standalone `createTable` hook and `createColumnHelper` method. You can choose to use either way.
// This example uses the new `createTableHook` method to create a re-usable table hook factory instead of independently using the standalone `useTable` hook and `createColumnHelper` method. You can choose to use either way.

// 1. Define what the shape of your data will be for each row
type Person = {
Expand Down Expand Up @@ -39,20 +39,25 @@ const defaultData: Array<Person> = [
status: 'Complicated',
progress: 10,
},
{
firstName: 'kevin',
lastName: 'vandy',
age: 28,
visits: 100,
status: 'Single',
progress: 70,
},
]

// 3. New in V9! Tell the table which features and row models we want to use. In this case, this will be a basic table with no additional features
const tableHelper = createTableHelper({
const { createAppTable, createAppColumnHelper } = createTableHook({
_features: {},
_rowModels: {}, // client-side row models. `Core` row model is now included by default, but you can still override it here

TData: {} as Person,
debugTable: true,
})

// 4. Create a helper object to help define our columns
// const { columnHelper } = tableHelper // if TData was set in the table helper options - otherwise use the createColumnHelper method below
const columnHelper = tableHelper.createColumnHelper<Person>()
const columnHelper = createAppColumnHelper<Person>()

// 5. Define the columns for your table with a stable reference (in this case, defined statically outside of a react component)
const columns = columnHelper.columns([
Expand Down Expand Up @@ -89,21 +94,28 @@ const columns = columnHelper.columns([
}),
])

function App() {
export function App() {
// 6. Store data with a stable reference
const [data, setData] = createSignal(defaultData)
const rerender = () => setData(defaultData)
const [data, setData] = createSignal([...defaultData])

// Helper to rerender with sorted data (by age ascending)
function rerender() {
setData((prev) =>
prev.slice().sort((a: Person, b: Person) => a.age - b.age),
)
}

// 7. Create the table instance with the required columns and data.
// Features and row models are already defined in the table helper object above
const table = tableHelper.createTable({
// Features and row models are already defined in the createTableHook call above
const table = createAppTable({
columns,
get data() {
return data()
},
// add additional table options here or in the table helper above
// add additional table options here or in the createTableHook call above
})

// 8. Render your table markup from the table instance APIs
return (
<div class="p-2">
<table>
Expand All @@ -114,12 +126,7 @@ function App() {
<For each={headerGroup.headers}>
{(header) => (
<th>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext(),
)}
<table.FlexRender header={header} />
</th>
)}
</For>
Expand All @@ -134,10 +141,7 @@ function App() {
<For each={row.getAllCells()}>
{(cell) => (
<td>
{flexRender(
cell.column.columnDef.cell,
cell.getContext(),
)}
<table.FlexRender cell={cell} />
</td>
)}
</For>
Expand All @@ -152,12 +156,7 @@ function App() {
<For each={footerGroup.headers}>
{(header) => (
<th>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.footer,
header.getContext(),
)}
<table.FlexRender footer={header} />
</th>
)}
</For>
Expand All @@ -167,11 +166,9 @@ function App() {
</tfoot>
</table>
<div class="h-4" />
<button onClick={() => rerender()} class="border p-2">
Rerender
<button onClick={rerender} class="border p-2">
Rerender (sort by age)
</button>
</div>
)
}

export default App
5 changes: 5 additions & 0 deletions examples/solid/basic-app-table/src/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { render } from 'solid-js/web'
import './index.css'
import { App } from './App'

render(() => <App />, document.getElementById('root') as HTMLElement)
23 changes: 23 additions & 0 deletions examples/solid/basic-external-state/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"name": "tanstack-table-example-solid-basic-external-state",
"version": "0.0.0",
"description": "",
"scripts": {
"start": "vite",
"dev": "vite",
"build": "vite build",
"serve": "vite preview",
"lint": "eslint ./src"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

ESLint script references missing dependency.

Same issue as in basic-external-store/package.json — the lint script uses eslint but it's not in devDependencies.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/solid/basic-external-state/package.json` at line 10, The "lint" NPM
script uses the eslint binary but eslint is not declared in devDependencies;
update package.json to add eslint as a devDependency (or replace the script to
invoke npx eslint) so the "lint" script ("lint": "eslint ./src") will work
consistently; e.g., add "eslint" under devDependencies and run the corresponding
install to ensure the CLI is available for that script.

},
"license": "MIT",
"devDependencies": {
"@faker-js/faker": "^10.2.0",
"typescript": "5.9.3",
"vite": "^7.3.1",
"vite-plugin-solid": "^2.11.10"
},
"dependencies": {
"@tanstack/solid-table": "^9.0.0-alpha.10",
"solid-js": "^1.9.11"
}
}
207 changes: 207 additions & 0 deletions examples/solid/basic-external-state/src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
import {
createColumnHelper,
createPaginatedRowModel,
createSortedRowModel,
flexRender,
rowPaginationFeature,
rowSortingFeature,
sortFns,
tableFeatures,
createTable,
} from '@tanstack/solid-table'
Comment on lines +1 to +11
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix the sort-imports lint error.

Line 10 leaves createTable out of alphabetical order in this named import list, so the ESLint check will stay red until it is moved above flexRender.

🧰 Tools
🪛 ESLint

[error] 10-10: Member 'createTable' of the import declaration should be sorted alphabetically.

(sort-imports)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/solid/basic-external-state/src/App.tsx` around lines 1 - 11, The
named imports from '@tanstack/solid-table' are not alphabetized: move
createTable so the import list is sorted alphabetically (i.e., place createTable
before flexRender) to satisfy the sort-imports lint rule; update the import
statement that currently lists createTable after flexRender so all symbols like
createColumnHelper, createPaginatedRowModel, createSortedRowModel, createTable,
flexRender, rowPaginationFeature, rowSortingFeature, sortFns, tableFeatures
appear in proper alphabetical order.

import { For, createSignal } from 'solid-js'
import { makeData } from './makeData'
import type { PaginationState, SortingState } from '@tanstack/solid-table'
import type { Person } from './makeData'

const _features = tableFeatures({
rowPaginationFeature,
rowSortingFeature,
})

const columnHelper = createColumnHelper<typeof _features, Person>()

const columns = columnHelper.columns([
columnHelper.accessor('firstName', {
header: 'First Name',
cell: (info) => info.getValue(),
}),
columnHelper.accessor('lastName', {
header: 'Last Name',
cell: (info) => info.getValue(),
}),
columnHelper.accessor('age', {
header: 'Age',
}),
columnHelper.accessor('visits', {
header: 'Visits',
}),
columnHelper.accessor('status', {
header: 'Status',
}),
columnHelper.accessor('progress', {
header: 'Profile Progress',
}),
])

function App() {
const [data] = createSignal(makeData(1000))
const [sorting, setSorting] = createSignal<SortingState>([])
const [pagination, setPagination] = createSignal<PaginationState>({
pageIndex: 0,
pageSize: 10,
})

const table = createTable({
_features,
_rowModels: {
sortedRowModel: createSortedRowModel(sortFns),
paginatedRowModel: createPaginatedRowModel(),
},
columns,
get data() {
return data()
},
state: {
get sorting() {
return sorting()
},
get pagination() {
return pagination()
},
},
onSortingChange: setSorting,
onPaginationChange: setPagination,
})

return (
<div class="p-2">
<table>
<thead>
<For each={table.getHeaderGroups()}>
{(headerGroup) => (
<tr>
<For each={headerGroup.headers}>
{(header) => (
<th colSpan={header.colSpan}>
{header.isPlaceholder ? null : (
<div
class={
header.column.getCanSort()
? 'cursor-pointer select-none'
: ''
}
onClick={header.column.getToggleSortingHandler()}
>
{flexRender(
header.column.columnDef.header,
header.getContext(),
)}
{{
asc: ' 🔼',
desc: ' 🔽',
}[header.column.getIsSorted() as string] ?? null}
</div>
Comment on lines +88 to +104
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Use a real button for sortable headers.

Lines 88-104 attach sorting to a plain <div>, so keyboard users cannot focus or toggle it. Please render a <button type="button"> for sortable headers and keep non-sortable headers as plain text.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/solid/basic-external-state/src/App.tsx` around lines 88 - 104,
Replace the clickable div used for sortable headers with a real button: where
the code currently checks header.column.getCanSort() and renders a div with
onClick={header.column.getToggleSortingHandler()}, render a <button
type="button"> when header.column.getCanSort() is true and retain a plain
non-interactive element for non-sortable headers; keep using
flexRender(header.column.columnDef.header, header.getContext()) for the label
and preserve the sort indicator computed from header.column.getIsSorted(), and
attach the toggle handler to the button (using
header.column.getToggleSortingHandler()) while keeping existing CSS/classes for
cursor and selection behavior.

)}
</th>
)}
</For>
</tr>
)}
</For>
</thead>
<tbody>
<For each={table.getRowModel().rows}>
{(row) => (
<tr>
<For each={row.getAllCells()}>
{(cell) => (
<td>
{flexRender(
cell.column.columnDef.cell,
cell.getContext(),
)}
</td>
)}
</For>
</tr>
)}
</For>
</tbody>
</table>
<div class="h-2" />
<div class="flex items-center gap-2">
<button
class="border rounded p-1"
onClick={() => table.setPageIndex(0)}
disabled={!table.getCanPreviousPage()}
>
{'<<'}
</button>
<button
class="border rounded p-1"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
{'<'}
</button>
<button
class="border rounded p-1"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
{'>'}
</button>
<button
class="border rounded p-1"
onClick={() => table.setPageIndex(table.getPageCount() - 1)}
disabled={!table.getCanNextPage()}
>
{'>>'}
Comment on lines +134 to +160
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add accessible names to the pager controls.

Lines 134-160 use symbol-only labels (<<, <, >, >>), which gives assistive tech vague button names. Add aria-labels for first/previous/next/last page.

♿ Suggested update
         <button
+          aria-label="First page"
           class="border rounded p-1"
           onClick={() => table.setPageIndex(0)}
           disabled={!table.getCanPreviousPage()}
         >
@@
         <button
+          aria-label="Previous page"
           class="border rounded p-1"
           onClick={() => table.previousPage()}
           disabled={!table.getCanPreviousPage()}
         >
@@
         <button
+          aria-label="Next page"
           class="border rounded p-1"
           onClick={() => table.nextPage()}
           disabled={!table.getCanNextPage()}
         >
@@
         <button
+          aria-label="Last page"
           class="border rounded p-1"
           onClick={() => table.setPageIndex(table.getPageCount() - 1)}
           disabled={!table.getCanNextPage()}
         >
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<button
class="border rounded p-1"
onClick={() => table.setPageIndex(0)}
disabled={!table.getCanPreviousPage()}
>
{'<<'}
</button>
<button
class="border rounded p-1"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
{'<'}
</button>
<button
class="border rounded p-1"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
{'>'}
</button>
<button
class="border rounded p-1"
onClick={() => table.setPageIndex(table.getPageCount() - 1)}
disabled={!table.getCanNextPage()}
>
{'>>'}
<button
aria-label="First page"
class="border rounded p-1"
onClick={() => table.setPageIndex(0)}
disabled={!table.getCanPreviousPage()}
>
{'<<'}
</button>
<button
aria-label="Previous page"
class="border rounded p-1"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
{'<'}
</button>
<button
aria-label="Next page"
class="border rounded p-1"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
{'>'}
</button>
<button
aria-label="Last page"
class="border rounded p-1"
onClick={() => table.setPageIndex(table.getPageCount() - 1)}
disabled={!table.getCanNextPage()}
>
{'>>'}
</button>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/solid/basic-external-state/src/App.tsx` around lines 134 - 160, The
pager buttons currently use only symbol labels which are inaccessible; update
each button element that invokes table.setPageIndex, table.previousPage,
table.nextPage and the last-page setPageIndex call to include descriptive
aria-label attributes (e.g., aria-label="First page", "Previous page", "Next
page", "Last page") so assistive technologies get meaningful names; keep
existing disabled logic and onClick handlers unchanged, just add the aria-labels
to the corresponding button elements.

</button>
<span class="flex items-center gap-1">
<div>Page</div>
<strong>
{pagination().pageIndex + 1} of {table.getPageCount()}
</strong>
</span>
<span class="flex items-center gap-1">
| Go to page:
<input
type="number"
min="1"
max={table.getPageCount()}
value={pagination().pageIndex + 1}
onInput={(e) => {
const page = e.currentTarget.value
? Number(e.currentTarget.value) - 1
: 0
table.setPageIndex(page)
}}
class="border p-1 rounded w-16"
/>
</span>
<select
value={pagination().pageSize}
onChange={(e) => {
table.setPageSize(Number(e.currentTarget.value))
}}
>
{[10, 20, 30, 40, 50].map((pageSize) => (
<option value={pageSize}>Show {pageSize}</option>
))}
</select>
</div>
<div class="h-4" />
<pre>
{JSON.stringify(
{ sorting: sorting(), pagination: pagination() },
null,
2,
)}
</pre>
</div>
)
}

export default App
Loading
Loading