javascriptreactjsnext.jsreact-hookstanstack-table

Tanstack react table managing globalFilter in URL in stead of state


I am using Tanstack react table in my NextJS application.

I have implemented a search field with globalFilter, worked fine.

I want to manage the state in the URL in stead of the state so search results and views are shareable between users of the app.

I have managed to do so but am fairly sure i am doing it wrong as I am managing state now in the state AND the URL. Should be only URL. I am confused if this is possible at all as you have to bind a state to the table but wonder if someone might know a solution.

This is my table:

'use client'

import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from '@/components/ui/table'
import {
  ColumnDef,
  ColumnFiltersState,
  flexRender,
  getCoreRowModel,
  getFilteredRowModel,
  getPaginationRowModel,
  useReactTable,
} from '@tanstack/react-table'
import { set } from 'date-fns'
import { useSearchParams, useRouter } from 'next/navigation'
import { useEffect, useState } from 'react'

interface DataTableProps<TData, TValue> {
  columns: ColumnDef<TData>[] | null
  data: TData[] | null
}

export const DataTable = <TData extends TValue, TValue>({
  columns,
  data,
}: DataTableProps<TData, TValue>) => {
  const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([])
  const [globalFilter, setGlobalFilter] = useState<string>('')
  const searchParams = useSearchParams()
  const router = useRouter()

  const q = searchParams.get('q') || ''

  const table = useReactTable({
    data: data || [],
    columns: columns || [],
    getCoreRowModel: getCoreRowModel(),
    getPaginationRowModel: getPaginationRowModel(),
    onColumnFiltersChange: setColumnFilters,
    getFilteredRowModel: getFilteredRowModel(),
    state: {
      columnFilters,
      globalFilter,
    },
    onGlobalFilterChange: setGlobalFilter,
  })

  useEffect(() => {
    setGlobalFilter(q)
  }, [globalFilter, q])

  //TODO i think global filter should be handled by the url only not also state
  return (
    <div>
      <div className='flex items-center justify-end pb-4 '>
        <Input
          placeholder='Search...'
          value={globalFilter}
          onChange={(e) => {
            setGlobalFilter(e.target.value)
            router.push(`?q=${e.target.value}`, { scroll: false })
          }}
          className='max-w-sm w-[250px]'
        />
      </div>
      <div>
        // table omitted for readability.
      </div>
      <div className='flex items-center justify-end space-x-2 py-4'>
        // pagination omitted for readability
      </div>
    </div>
  )
}

Solution

  • Try binding globalFilter to your url searchParams in table config Object like so: state: { columnFilters, globalFilter: q, }, Then simply remove the [globalFilter, setGlobalFilter] useState() call and any related code (remove onGlobalFilterChange from table config, remove useEffect, remove setGlobalFilter from onChange handler, change Input value to value={q})