TNKS Data Table

URL State Management

Persist table state in the URL for bookmarking, sharing, and browser history support

URL State Management

The DataTable component includes built-in URL state management that persists table state in the URL, enabling users to bookmark specific views, share links with colleagues, and use browser navigation.

Why URL State?

URL state management provides several key benefits:

share

Shareable URLs

Users can share links that preserve their exact view of the data - filters, sorting, pagination, and all.

bookmark

Bookmarkable

Save specific data views as browser bookmarks for quick access to frequently used filters.

history

Browser History

Forward and back navigation works naturally - each table state change is a history entry.

search

SEO Benefits

Search engines can index different filtered views of your data.

How It Works

The component uses a custom useUrlState hook that leverages Next.js App Router's built-in search params utilities. All table state is automatically synced with the URL.

Implementation

// Example URL with state
/users?page=2&pageSize=20&sortBy=created_at&sortOrder=desc&search=john

When a user changes any table state (sorts, filters, searches), the URL updates automatically. When they reload the page or share the link, the table restores to that exact state.

Managed URL Parameters

The following table states are persisted in the URL:

ParameterDescriptionExample
pageCurrent page numberpage=2
pageSizeNumber of rows per pagepageSize=20
searchGlobal search querysearch=john
sortByColumn being sortedsortBy=created_at
sortOrderSort directionsortOrder=desc
dateRangeDate filter rangedateRange={"from_date":"2024-01-01","to_date":"2024-12-31"}
columnVisibilityVisible/hidden columnscolumnVisibility={"id":false,"email":true}
columnFiltersColumn-specific filterscolumnFilters=[...]

Configuration

Enable URL State (Default)

URL state is enabled by default:

config={{
  enableUrlState: true,  // Default value
}}

Disable URL State

For tables where state persistence isn't needed:

config={{
  enableUrlState: false,  // State won't sync to URL
}}

Usage Examples

Simple Example

src/app/(section)/users/page.tsx
import { Suspense } from "react";
import UsersTable from "./users-table";

export default function UsersPage() {
  return (
    // Suspense is required for URL state
    <Suspense fallback={<div>Loading...</div>}>
      <UsersTable />
    </Suspense>
  );
}

URL Examples

Paginated View

/users?page=3&pageSize=50

Sorted View

/users?sortBy=name&sortOrder=asc

Filtered View

/users?search=john&dateRange={"from_date":"2024-01-01","to_date":"2024-12-31"}

Complete View

/users?page=2&pageSize=20&sortBy=created_at&sortOrder=desc&search=active&dateRange={"from_date":"2024-01-01","to_date":"2024-12-31"}&columnVisibility={"id":false}

Custom useUrlState Hook

The component uses a custom hook that provides type-safe URL state management:

const [page, setPage] = useUrlState("page", 1);
const [search, setSearch] = useUrlState("search", "");
const [dateRange, setDateRange] = useUrlState<DateRange>(
  "dateRange",
  { from_date: "", to_date: "" }
);

Features

  • Type Safety: Full TypeScript support with generics
  • Auto Serialization: Automatically handles strings, numbers, booleans, objects, and arrays
  • No Dependencies: Uses only Next.js built-in utilities
  • Automatic Sync: URL updates when state changes, state updates when URL changes

Serialization

The hook automatically serializes different types:

TypeURL Representation
Numberpage=2
Stringsearch=john
Booleanactive=true
ObjectdateRange={"from_date":"2024-01-01","to_date":"2024-12-31"}
Arrayfilters=["active","verified"]

Real-World Scenarios

Scenario 1: Customer Support

Support agent finds a bug in filtered data and shares the exact view with engineering:

/users?search=error&dateRange={"from_date":"2024-11-01","to_date":"2024-11-16"}&sortBy=created_at&sortOrder=desc

Engineering team opens the link and sees the exact same filtered view.

Scenario 2: Bookmarked Reports

Marketing team bookmarks frequently used views:

# Active customers from last month
/users?dateRange={"from_date":"2024-10-01","to_date":"2024-10-31"}&columnVisibility={"internal_notes":false}

# High-value customers
/users?sortBy=total_spent&sortOrder=desc&pageSize=50

Scenario 3: Dashboard Sharing

Manager shares specific data views with team members:

# Team performance view
/users?sortBy=performance_score&sortOrder=desc&dateRange={"from_date":"2024-01-01","to_date":"2024-12-31"}

Important Considerations

Suspense Requirement

URL state requires wrapping the table in a Suspense boundary:

// ✅ Correct
<Suspense fallback={<div>Loading...</div>}>
  <UsersTable />
</Suspense>

// ❌ Wrong
<UsersTable />  // Will cause errors

Client Component

Tables using URL state must be client components:

"use client";  // ✅ Required

import { DataTable } from "@/components/data-table/data-table";

URL Length Limits

Be aware of URL length limitations (~2000 characters in most browsers):

// ✅ Good: Simple state
?page=2&search=john

// ⚠️ Caution: Large complex objects
?columnVisibility={"field1":true,"field2":false,...100 more}

For very complex state, consider using localStorage or database-backed presets instead.

Performance Optimizations

Debouncing (Future)

Currently, URL updates immediately on state changes. For frequently changing values like search, debouncing is recommended:

// Future enhancement
const [debouncedSearch, setDebouncedSearch] = useUrlState(
  "search",
  "",
  { debounce: 300 }  // 300ms delay
);

Default Value Cleanup

The hook doesn't add parameters that match default values:

// If default page is 1 and current page is 1:
// URL: /users (no page parameter)

// If current page is 2:
// URL: /users?page=2

This keeps URLs clean and readable.

Comparison with Other State Management

ApproachShareableBookmarkableHistoryPersistence
URL State✅ Yes✅ Yes✅ YesBrowser only
Local State❌ No❌ No❌ NoNone
localStorage❌ No✅ Yes❌ NoBrowser only
Database✅ Yes✅ Yes❌ NoServer

URL state is ideal for:

  • Tables where users share specific views
  • Dashboards with bookmarkable reports
  • Admin panels with frequently used filters

Other approaches may be better for:

  • Temporary UI state (use local state)
  • Persistent user preferences across devices (use database)
  • Large complex state (use localStorage or database)

Troubleshooting

Issue: State not persisting

Check:

  • Is the table wrapped in Suspense?
  • Is enableUrlState set to true?
  • Is the component a client component?

Issue: URL not updating

Check:

  • Ensure you're using Next.js 13+ with App Router
  • Verify the hook is called at component top level
  • Check browser console for errors

Issue: Page refresh loses state

This is expected if:

  • enableUrlState is false
  • The component isn't using the URL state system

Issue: URL too long error

Solution:

  • Reduce number of URL parameters
  • Use localStorage for complex state
  • Implement server-side saved views

Next Steps

How is this guide?