DEV Community

Cover image for Building a Library to Sync TanStack Table State with URL Parameters
taro
taro

Posted on

Building a Library to Sync TanStack Table State with URL Parameters

Tanstack Table is a highly customizable, headless UI library for building powerful tables in various frameworks, including React, Vue, and Solid.

I’ve developed a library called tanstack-table-search-params. This library syncs TanStack Table's state—such as search and sorting—with URL parameters in React applications.

GitHub logo taro-28 / tanstack-table-search-params

React Hook for syncing TanStack Table state with URL search params.

TanStack Table Search Params

NPM Version NPM Downloads GitHub Repo stars Bundlephobia Minzipped size

React Hook for syncing TanStack Table state with URL search params.

tanstack-table-search-params.mp4

🚀 Quick Start

First, install the package.

npm i tanstack-table-search-params
Enter fullscreen mode Exit fullscreen mode

For example, if you are using Next.js (Pages Router), you can use the hook like this.

import { useReactTable } from "tanstack-table";
import { useRouter } from "next/router";
import { useTableSearchParams } from "tanstack-table-search-params";
const router = useRouter();

// Get state and onChanges
const stateAndOnChanges = useTableSearchParams({
  query: router.query,
  pathname: router.pathname,
  replace: router.replace,
  // or
  push: router.push,
});

const table = useReactTable({
  // Set state and onChanges
  ...stateAndOnChanges,
  data,
  columns,
  getCoreRowModel: getCoreRowModel(),
  getFilteredRowModel: getFilteredRowModel(),
  getSortedRowModel: getSortedRowModel(),
  // ... other options
Enter fullscreen mode Exit fullscreen mode

TanStack Table in React

Here’s a quick introduction to building tables with TanStack Table in React.

TanStack Table provides a useReactTable hook, which takes in data and column definitions. It returns the table's state and handlers that you can use to render the table.

import {
  createColumnHelper,
  flexRender,
  getCoreRowModel,
  useReactTable,
} from "@tanstack/react-table";

type User = { id: string; name: string };

// Sample data
const data: User[] = [
  { id: "1", name: "John" },
  { id: "2", name: "Sara" },
];

// Column definitions
const columnHelper = createColumnHelper<User>();
const columns = [
  columnHelper.accessor("id", {}),
  columnHelper.accessor("name", {}),
];

export default function UserTable() {
  // Create table
  const table = useReactTable({
    data,
    columns,
    getCoreRowModel: getCoreRowModel(),
  });

  return (
    <div>
      <table>
        <thead>
          <tr>
            {table.getFlatHeaders().map((header) => (
              <th key={header.id}>
                {flexRender(
                  header.column.columnDef.header,
                  header.getContext(),
                )}
              </th>
            ))}
          </tr>
        </thead>
        <tbody>
          {/* Render table rows */}
          {table.getRowModel().rows.map((row) => (
            <tr key={row.id}>
              {row.getAllCells().map((cell) => (
                <td key={cell.id}>
                  {flexRender(cell.column.columnDef.cell, cell.getContext())}
                </td>
              ))}
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Here is the resulting table:

Table created by TanStack Table

Next, I implemented a search feature for this table, as shown below.

import {
  // ...
+ getFilteredRowModel,
} from "@tanstack/react-table";

// ...

export default function UserTable() {
  const table = useReactTable({
    data,
    columns,
    getCoreRowModel: getCoreRowModel(),
+   getFilteredRowModel: getFilteredRowModel(),
  });

  return (
    <div>
+     <input
+       placeholder="Search..."
+       value={table.getState().globalFilter}
+       onChange={(e) => table.setGlobalFilter(e.target.value)}
+     />
      <table>
Enter fullscreen mode Exit fullscreen mode

The table now supports basic search functionality:

Table with search

Here, getCoreRowModel and getFilteredRowModel are functions that implement various table features called RowModels. In TanStack Table, you can use built-in RowModels like getFilteredRowModel, or create custom RowModels to extend functionality, such as adding search or sorting capabilities to your table.

Introducing tanstack-table-search-params

tanstack-table-search-params simplifies the synchronization of TanStack Table state with URL parameters. For example, in a Next.js Pages Router project, you only need to:

  1. Import and call useTableSearchParams, passing the useRouter object.
  2. Pass the hook’s return value to useReactTable.
+ import { useRouter } from "next/router";
+ import { useTableSearchParams } from "tanstack-table-search-params";

// ...

export default function UserTable() {
+ const router = useRouter();
+ const stateAndOnChanges = useTableSearchParams(router);
  const table = useReactTable({
+   ...stateAndOnChanges,
    // ...
Enter fullscreen mode Exit fullscreen mode

The result:

Table with search params

How It Works

To explain how tanstack-table-search-params synchronizes the state of TanStack Table with URL parameters, we will modify the code above to pass only the necessary properties, instead of passing the entire router object and stateAndOnChanges object.

export default function UserTable() {
  const { query, pathname, replace} = useRouter();
  const stateAndOnChanges = useTableSearchParams({
    query: router.query,
    pathname: router.pathname,
    replace: router.replace,
  });
  const table = useReactTable({
    state: {
        globalFilter: stateAndOnChanges.state.globalFilter,
    },
    onGlobalFilterChange: stateAndOnChanges.onGlobalFilterChange,
    // ...
Enter fullscreen mode Exit fullscreen mode

The reason for passing state.globalFilter and onGlobalFilterChange to useReactTable is to configure the setup for managing TanStack Table state outside of useReactTable.

https://tanstack.com/table/latest/docs/guide/global-filtering#global-filter-state

As shown in the code above, useTableSearchParams takes the following:

  • query: The React state for URL parameters
  • pathname: The current URL path
  • replace (or push): The function to replace (or push) the URL

https://github.com/taro-28/tanstack-table-search-params/blob/main/packages/tanstack-table-search-params/src/index.ts#L123-L151

and returns:

  • state.globalFilter
  • onGlobalFilterChange

https://github.com/taro-28/tanstack-table-search-params/blob/main/packages/tanstack-table-search-params/src/index.ts#L23-L44

As you can imagine from the arguments and return values, this hook is very simple in its functionality.

First, state decodes the query into the following TanStack Table TableState type and returns it:

Next, onGlobalFilterChange simply returns a function that encodes each part of the TanStack Table state into URL parameters and executes replace.

Both the processes of creating state and onGlobalFilterChange are merely transformations (object → object, function → function), and neither of them holds any internal state. (The only exception is for debouncing, which will be discussed later, where internal state is used for debounce purposes.)

Supported TanStack Table States

Currently, the library supports the following four TanStack Table states, with plans to expand support in the future:

  • globalFilter
  • sorting
  • pagination
  • columnFilters

Supported Routers

As introduced in the How It Works, tanstack-table-search-params simply converts the React state of URL parameters into TanStack Table state. This means it should (probably) work with any router that allows you to retrieve URL parameters as React state.

Examples are available for the following three routers. Feel free to check them out:

Additional Customization Options

With tanstack-table-search-params, you can customize URL parameter names and encoding formats, etc.

URL Parameter Names

By default, URL parameter names match the names in the TableState type, such as globalFilter or sorting.

To change the URL parameter names, use the paramNames option as the second argument of useTableSearchParams.

const stateAndOnChanges = useTableSearchParams(router, {
  paramNames: {
    // Change the URL parameter name
    globalFilter: "search",
  },
});
Enter fullscreen mode Exit fullscreen mode

If you want to add a prefix or suffix to the parameter names, you can pass a function.

const stateAndOnChanges = useTableSearchParams(router, {
  paramNames: {
    // Add a prefix to the URL parameter name
    globalFilter: (defaultName) => `userTable-${defaultName}`,
  },
});
Enter fullscreen mode Exit fullscreen mode

Encoding Formats

By default, the encoding format for URL parameter values is a straightforward format, as shown below:

State Value Example URL Parameters
Global Search ?globalFilter=John
Sorting ?sorting=name.desc
Pagination ?pageIndex=2&pageSize=20

To change the encoding format, you can specify custom encoders and decoders in the second argument of useTableSearchParams.

const stateAndOnChanges = useTableSearchParams(router, {
  encoders: {
    // Change encoding format to use JSON.stringify
    globalFilter: (globalFilter) => ({
      globalFilter: JSON.stringify(globalFilter),
    }),
  },
  decoders: {
    globalFilter: (query) =>
      query["globalFilter"]
        ? JSON.parse(query["globalFilter"])
        : (query["globalFilter"] ?? ""),
  },
});
Enter fullscreen mode Exit fullscreen mode

You can also modify both the encoding format and parameter names.

const stateAndOnChanges = useTableSearchParams(router, {
  encoders: {
    sorting: (sorting) => ({
      // Change encoding format to JSON.stringify and rename parameter to my-sorting
      "my-sorting": JSON.stringify(sorting),
    }),
  },
  decoders: {
    sorting: (query) =>
      query["my-sorting"]
        ? JSON.parse(query["my-sorting"])
        : query["my-sorting"],
  },
});
Enter fullscreen mode Exit fullscreen mode

Debounce

You can debounce the synchronization of state with URL parameters.

const stateAndOnChanges = useTableSearchParams(router, {
  debounceMilliseconds: 500,
});
Enter fullscreen mode Exit fullscreen mode

It’s also possible to debounce only specific TanStack Table states.

const stateAndOnChanges = useTableSearchParams(router, {
  debounceMilliseconds: {
    // Debounce synchronization of globalFilter with URL parameters by 0.5 seconds
    globalFilter: 500,
  },
});
Enter fullscreen mode Exit fullscreen mode

Default Values

You can specify default values for TanStack Table states when URL parameters are absent.

For example, without setting a default value for sorting, the state.sorting value (sorting condition) and the corresponding URL parameter behave as follows:

state.sorting Value URL Parameter
[] None
[{ id: "createdAt", desc: true }] ?sorting=createdAt.desc
[{ id: "createdAt", desc: false }] ?sorting=createdAt.asc

(Note: createdAt is the column name.)

In contrast, if you set the default sorting order to descending as shown below:

const stateAndOnChanges = useTableSearchParams(router, {
  defaultValues: {
    sorting: [{ id: "createdAt", desc: true }],
  },
});
Enter fullscreen mode Exit fullscreen mode

The state.sorting value and the corresponding URL parameter will behave as follows:

state.sorting Value URL Parameter
[] ?sorting=none
[{ id: "createdAt", desc: true }] None
[{ id: "createdAt", desc: false }] ?sorting=createdAt.asc

In Conclusion

This library originated from my work, where I needed a simple way to sync TanStack Table states with URL parameters.

So far, I’ve implemented all the features I personally needed. Moving forward, I plan to enhance its completeness by supporting more TanStack Table states and expanding customization options.

By the way, this is the first time I’ve published one of my own packages on npm. It was a great experience to learn how to publish on npm and discover the convenience of tools like tsup.

I’d be thrilled if you gave it a try! (And I’d be even happier if you starred the repository!)

GitHub logo taro-28 / tanstack-table-search-params

React Hook for syncing TanStack Table state with URL search params.

TanStack Table Search Params

NPM Version NPM Downloads GitHub Repo stars Bundlephobia Minzipped size

React Hook for syncing TanStack Table state with URL search params.

tanstack-table-search-params.mp4

🚀 Quick Start

First, install the package.

npm i tanstack-table-search-params
Enter fullscreen mode Exit fullscreen mode

For example, if you are using Next.js (Pages Router), you can use the hook like this.

import { useReactTable } from "tanstack-table";
import { useRouter } from "next/router";
import { useTableSearchParams } from "tanstack-table-search-params";
const router = useRouter();

// Get state and onChanges
const stateAndOnChanges = useTableSearchParams({
  query: router.query,
  pathname: router.pathname,
  replace: router.replace,
  // or
  push: router.push,
});

const table = useReactTable({
  // Set state and onChanges
  ...stateAndOnChanges,
  data,
  columns,
  getCoreRowModel: getCoreRowModel(),
  getFilteredRowModel: getFilteredRowModel(),
  getSortedRowModel: getSortedRowModel(),
  // ... other options
Enter fullscreen mode Exit fullscreen mode

Top comments (0)