Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs(react-table): add TanStack Table community parsers example / documentation #836

Open
wants to merge 6 commits into
base: next
Choose a base branch
from
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
2 changes: 1 addition & 1 deletion packages/docs/content/docs/options.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ useQueryState('foo', {
```

<Callout title="Note">
the state returned by the hook is always updated **instantly**, to keep UI responsive.
The state returned by the hook is always updated **instantly**, to keep UI responsive.
Only changes to the URL, and server requests when using `shallow: false`, are throttled.
</Callout>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { ColumnFilter } from '@tanstack/react-table'
import { createParser, parseAsArrayOf, useQueryState } from 'nuqs'

// Each column filter is represented as `columnId=value`,
// for example: `[email protected]&age=["7",null]`
const filterParser = createParser({
parse: query => {
const [id, value] = query.split('=')
return {
id,
value: JSON.parse(value ?? '')
} as ColumnFilter
},
serialize: value => `${value.id}=${JSON.stringify(value.value)}`,
// This is a simple equality check for comparing objects.
// It works by converting both objects to strings and comparing them.
// For more robust deep equality, consider using lodash's isEqual.
eq: (a, b) => JSON.stringify(a) === JSON.stringify(b)
})

const parseAsColumnFiltersState = parseAsArrayOf(filterParser).withDefault([])

export function useColumnFiltersSearchParams(key = 'filter') {
return useQueryState(key, parseAsColumnFiltersState)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { createParser, parseAsInteger, useQueryStates } from 'nuqs'

// The page index parser is zero-indexed internally,
// but one-indexed when rendered in the URL,
// to align with your UI and what users might expect.
const pageIndexParser = createParser({
parse: query => {
const page = parseAsInteger.parse(query)
return page === null ? null : page - 1
},
serialize: value => {
return parseAsInteger.serialize(value + 1)
}
})

const paginationParsers = {
pageIndex: pageIndexParser.withDefault(0),
pageSize: parseAsInteger.withDefault(10)
}
const paginationUrlKeys = {
pageIndex: 'page',
pageSize: 'perPage'
}

export function usePaginationSearchParams(urlKeys = paginationUrlKeys) {
return useQueryStates(paginationParsers, {
urlKeys
})
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { ColumnSort, SortDirection } from '@tanstack/react-table'
import { createParser, parseAsArrayOf, useQueryState } from 'nuqs'

// Each sort is represented as `columnId:direction`,
// for example: `?orderBy=email:desc,status:asc`
const sortParser = createParser<ColumnSort>({
parse: query => {
const [id, direction] = query.split(':')
return {
id,
desc: direction === 'desc'
}
},
serialize: value =>
`${value.id}:${value.desc ? 'desc' : 'asc'}` as `${string}:${SortDirection}`,
// This is a simple equality check for comparing objects.
// It works by converting both objects to strings and comparing them.
// For more robust deep equality, consider using lodash's isEqual.
eq: (a, b) => JSON.stringify(a) === JSON.stringify(b)
})

const parseAsSortingState = parseAsArrayOf(sortParser).withDefault([])

export function useSortingSearchParams(key = 'orderBy') {
return useQueryState(key, parseAsSortingState)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
'use client'

import { CodeBlock } from '@/src/components/code-block.client'
import { Querystring } from '@/src/components/querystring'
import { TypescriptIcon } from '@/src/components/typescript-icon'
import { Input } from '@/src/components/ui/input'
import { Label } from '@/src/components/ui/label'
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue
} from '@/src/components/ui/select'
import { Separator } from '@/src/components/ui/separator'
import { parseAsString, useQueryState } from 'nuqs'
import { useDeferredValue } from 'react'
import { useColumnFiltersSearchParams } from './search-params.column-filters'
import { useTanStackTable } from './tanstack-table'

export function TanStackTableColumnFiltering() {
const [columnFiltersUrlKey, setColumnFiltersUrlKey] = useQueryState(
'columnFiltersUrlKey',
parseAsString.withDefault('filter')
)
const [columnFilters, setColumnFilters] =
useColumnFiltersSearchParams(columnFiltersUrlKey)

const parserCode =
useDeferredValue(`import { ColumnFilter } from '@tanstack/react-table'
import { createParser, parseAsArrayOf, useQueryState } from 'nuqs'

// Each column filter is represented as \`columnId=value\`,
// for example: \`[email protected]&age=["7",null]\`
const filterParser = createParser({
parse: query => {
const [id, value] = query.split('=')
return {
id,
value: JSON.parse(value ?? '')
} as ColumnFilter
},
serialize: value => \`\${value.id}=\${JSON.stringify(value.value)}\`,
// This is a simple equality check for comparing objects.
// It works by converting both objects to strings and comparing them.
// For more robust deep equality, consider using lodash's isEqual.
eq: (a, b) => JSON.stringify(a) === JSON.stringify(b)
})

const parseAsColumnFiltersState = parseAsArrayOf(filterParser).withDefault([])

export function useColumnFiltersSearchParams() {
return useQueryState('${columnFiltersUrlKey}', parseAsColumnFiltersState)
}`)

const table = useTanStackTable({
data: [],
state: { columnFilters },
onColumnFiltersChange: setColumnFilters,
enableSorting: false
})

const internalState = useDeferredValue(
JSON.stringify(table.getState().columnFilters, null, 2)
)

return (
<section>
<div className="not-prose flex flex-col gap-2 rounded-xl border border-dashed p-2">
<div className="grid grid-cols-3 items-center gap-2">
<Input
placeholder="Filter emails..."
type="search"
className="col-span-2"
value={(table.getColumn('email')?.getFilterValue() as string) ?? ''}
onChange={event =>
table.getColumn('email')?.setFilterValue(event.target.value)
}
/>
<Select
value={
(table.getColumn('status')?.getFilterValue() as string) ?? ''
}
onValueChange={value =>
table.getColumn('status')?.setFilterValue(value)
}
>
<SelectTrigger>
<SelectValue placeholder="Status" />
</SelectTrigger>
<SelectContent>
<SelectItem value="pending">Pending</SelectItem>
<SelectItem value="processing">Processing</SelectItem>
<SelectItem value="success">Success</SelectItem>
<SelectItem value="failed">Failed</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<p className="mb-0">
Configure and copy-paste this parser into your application:
</p>
<div className="flex flex-col gap-6 xl:flex-row">
<CodeBlock
title="search-params.filtering.ts"
lang="ts"
icon={<TypescriptIcon />}
className="flex-grow"
code={parserCode}
/>
<aside className="w-full space-y-4 xl:w-64">
<Querystring
value={`?${columnFiltersUrlKey}=${columnFilters
?.map(filter => `${filter.id}=${JSON.stringify(filter.value)}`)
.join('&')}`}
/>
<CodeBlock
allowCopy={false}
title="Internal state"
code={internalState}
/>
<Separator className="my-8" />
<div className="space-y-2">
<Label htmlFor="columnFiltersKey">Column filters URL key</Label>
<Input
id="columnFiltersKey"
value={columnFiltersUrlKey}
onChange={e => {
setColumnFilters([])
setColumnFiltersUrlKey(e.target.value)
}}
placeholder="e.g., where"
/>
</div>
</aside>
</div>
</section>
)
}
Loading