npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@leancodepl/antd-table-hooks

v10.2.1

Published

Ant Design Table hooks for React

Readme

@leancodepl/antd-table-hooks

React hooks for managing Ant Design table state — sorting, pagination, and filters — with optional URL query parameter persistence via Zod schemas.

Installation

npm install @leancodepl/antd-table-hooks
yarn add @leancodepl/antd-table-hooks

API

useTable(props)

Orchestrates table sorting, pagination, and filter state through URL query parameters. Reads current state from queryParams and writes updates via setQueryParams, enabling shareable links, browser navigation, and state restoration on page reload.

Parameters:

| Name | Type | Description | | ---------------------------- | ------------------------------------------- | --------------------------------------------------------------------------------- | | props.queryParams | Record<string, unknown> | Current URL search params object (e.g. from TanStack Router's useSearch) | | props.setQueryParams | (params: Record<string, unknown>) => void | Callback to update URL search params | | props.definedFilters | FilterDefinition[] | Filter definitions created by defineFilters | | props.tableId | string | Unique prefix for URL params to avoid collisions between tables on the same route | | props.defaultSortKey | Key | Initial sort column key | | props.defaultSortDirection | SortOrder | Initial sort direction ("ascend" or "descend") | | props.defaultPageSize | number (optional) | Initial page size (defaults to 100) |

Returns: { filters, pagination, sorting } — state objects to pass to useFilters, usePagination, and useSorting.

useSorting(props)

Manages table column sorting state. Supports two modes: URL-driven (via useTable().sorting) or standalone with local state.

Parameters (URL-driven mode):

| Name | Type | Description | | --------------------- | ---------------------------------------------------- | -------------------------- | | props.sortKey | Key | Current sort column key | | props.sortDirection | SortOrder | Current sort direction | | props.onSortUpdate | (sortKey?: Key, sortDirection?: SortOrder) => void | Callback when sort changes |

Parameters (standalone mode):

| Name | Type | Description | | ---------------------------- | --------------------------------------------------------------- | -------------------------- | | props.defaultSortKey | Key (optional) | Initial sort column key | | props.defaultSortDirection | SortOrder (optional) | Initial sort direction | | props.onSortUpdate | (sortKey?: Key, sortDirection?: SortOrder) => void (optional) | Callback when sort changes |

Returns: { sortData, sortKey, sortDirection, isDescending }

  • sortData — pass to an Ant Design table's sort prop
  • sortKey — current sort column key
  • sortDirection — current sort direction ("ascend" | "descend")
  • isDescendingtrue when sortDirection is "descend"

usePagination(props)

Manages table pagination state with zero-indexed page output for API calls. Returns a getTablePagination helper that produces an Ant Design TablePaginationConfig.

Parameters (URL-driven mode):

| Name | Type | Description | | -------------------------- | ------------------------------------------------------------ | -------------------------------- | | props.displayPage | number | Current display page (1-indexed) | | props.pageSize | number | Current page size | | props.onPaginationChange | (props: { displayPage: number; pageSize: number }) => void | Callback when pagination changes |

Parameters (standalone mode, all optional):

| Name | Type | Description | | -------------------------- | ----------------------------------------------------------------------- | -------------------------------------- | | props.initialDisplayPage | number (optional) | Initial display page (defaults to 1) | | props.initialPageSize | number (optional) | Initial page size (defaults to 100) | | props.onPaginationChange | (props: { displayPage: number; pageSize: number }) => void (optional) | Callback when pagination changes |

Returns: { page, pageSize, getTablePagination, resetPage }

  • page — zero-indexed page number for API calls
  • pageSize — current page size
  • getTablePagination(total?) — returns an Ant Design TablePaginationConfig
  • resetPage() — resets to page 1

defineFilters()

Creates a typed filter definition factory along with a combined Zod search schema for URL param validation. Call with a query type generic, then pass an array of filter definitions (or a factory function for context-dependent filters).

Returns: A function that accepts filter definitions and returns { searchSchema, filters }.

  • searchSchema — Zod schema for filter-related URL params, used with tableSearchSchema
  • filters — the filter definitions array (or factory function) to pass to useFilters

useFilters(props)

Manages filter state and produces an applyFilters function that applies all active filters to a query object.

Parameters:

| Name | Type | Description | | ----------------------- | -------------------------------------- | ------------------------------------------------------------- | | props.filters | FilterDefinition[] | Array of filter definitions (from defineFilters) | | props.onFiltersChange | (filters, values) => void (optional) | Callback invoked when any filter value changes | | props.initialValues | Partial<TValues> (optional) | Initial filter values (e.g. from useTable().filters.values) |

Returns: { filters, applyFilters, filterComponents, resetFilters, anyFilterSet }

  • filters — convenience wrapper object containing applyFilters, filterComponents, resetFilters, and anyFilterSet
  • applyFilters(query) — applies all active filter transforms to the query object
  • filterComponents — array of React nodes to render filter UI
  • resetFilters() — clears all filters
  • anyFilterSettrue when at least one filter is active

tableSearchSchema(filtersSearchSchema, tableId, sortKeySchema)

Combines filter, pagination, and sorting schemas into a single Zod schema for route validation.

Parameters:

| Name | Type | Description | | --------------------- | --------------------- | ----------------------------------------------------------- | | filtersSearchSchema | DefinedSearchSchema | Search schema returned by defineFilters | | tableId | string | Unique table identifier used to prefix parameter names | | sortKeySchema | ZodType | Zod schema for the sort key type (e.g. z.coerce.number()) |

Returns: Combined Zod object schema covering all table URL parameters.

buildPaginationSearchSchema(tableId)

Creates a Zod schema for pagination URL parameters ({tableId}-displayPage, {tableId}-pageSize).

Parameters:

| Name | Type | Description | | --------- | -------- | ------------------------------------------------------ | | tableId | string | Unique table identifier used to prefix parameter names |

Returns: Zod object schema for pagination params.

buildSortingSearchSchema(tableId, sortKeySchema)

Creates a Zod schema for sorting URL parameters ({tableId}-sortKey, {tableId}-sortDescending).

Parameters:

| Name | Type | Description | | --------------- | --------- | ----------------------------------------------------------- | | tableId | string | Unique table identifier used to prefix parameter names | | sortKeySchema | ZodType | Zod schema for the sort key type (e.g. z.coerce.number()) |

Returns: Zod object schema for sorting params.

Usage Examples

Full Table with URL Query State

The primary pattern — persists sorting, pagination, and filters in URL search params for shareable links and state restoration:

import { useSearch, useNavigate } from "@tanstack/react-router"
import { keepPreviousData } from "@tanstack/react-query"
import {
  useTable,
  useSorting,
  usePagination,
  useFilters,
  defineFilters,
  tableSearchSchema,
  InferFiltersSchema,
} from "@leancodepl/antd-table-hooks"
import z from "zod"

const tableId = "reviews"

const { searchSchema: filtersSearchSchema, filters: reviewFilters } = defineFilters<SearchQuery>()([
  emailFilter,
  statusFilter,
])

export const searchSchema = tableSearchSchema(filtersSearchSchema, tableId, z.coerce.number())

export function ReviewsTable() {
  const search = useSearch({ from: "/reviews" })
  const navigate = useNavigate({ from: "/reviews" })

  const queryState = useTable({
    queryParams: search,
    setQueryParams: params => navigate({ search: params }),
    definedFilters: reviewFilters,
    tableId,
    defaultSortKey: SortKey.DateCreated,
    defaultSortDirection: "descend",
  })

  const { sortData, sortKey, isDescending } = useSorting(queryState.sorting)
  const { page, pageSize, getTablePagination, resetPage } = usePagination(queryState.pagination)

  const { applyFilters, filters } = useFilters<SearchQuery, InferFiltersSchema<typeof reviewFilters>>({
    filters: reviewFilters,
    initialValues: queryState.filters.values,
    onFiltersChange: (_, values) => {
      queryState.filters.onFiltersChange(values)
      resetPage()
    },
  })

  const { data, isPending, isPlaceholderData } = api.useSearchReviews(
    applyFilters({ PageNumber: page, PageSize: pageSize, SortBy: sortKey, SortByDescending: isDescending }),
    { placeholderData: keepPreviousData },
  )

  return (
    <Table
      columns={columns}
      dataSource={data?.items}
      loading={isPending || isPlaceholderData}
      pagination={getTablePagination(data?.totalCount)}
    />
  )
}

Route Configuration

Use the combined search schema as validateSearch in a TanStack Router route:

import { createFileRoute } from "@tanstack/react-router"
import { searchSchema } from "./ReviewsTable"

export const Route = createFileRoute("/reviews")({
  component: ReviewsPage,
  validateSearch: searchSchema,
})

Standalone Table (Without URL State)

Use useSorting, usePagination, and useFilters directly when URL persistence is not needed:

import { useSorting, usePagination, useFilters } from "@leancodepl/antd-table-hooks"
import { keepPreviousData } from "@tanstack/react-query"

export function SimpleTable() {
  const { sortData, sortKey, isDescending } = useSorting({
    defaultSortDirection: "descend",
    defaultSortKey: SortKey.CreatedAt,
  })

  const { page, pageSize, getTablePagination, resetPage } = usePagination()

  const { applyFilters, filters } = useFilters({
    filters: simpleFilters,
    onFiltersChange: resetPage,
  })

  const { data, isPending } = api.useSearch(
    applyFilters({ PageNumber: page, PageSize: pageSize, SortBy: sortKey, SortByDescending: isDescending }),
    { placeholderData: keepPreviousData },
  )

  return (
    <Table
      columns={columns}
      dataSource={data?.items}
      loading={isPending}
      pagination={getTablePagination(data?.totalCount)}
    />
  )
}

Defining Filters

Filters are defined outside the component using defineFilters. Each filter is a FilterDefinition object describing its ID, component, search schema entries, and how it transforms the query:

import { defineFilters, FilterDefinition } from "@leancodepl/antd-table-hooks"

const tableId = "users"

const { searchSchema: usersFiltersSchema, filters: userFilters } = defineFilters<SearchQuery>()([
  emailTextFilter,
  stateSelectFilter,
  createdAtDateRangeFilter,
])

For context-dependent filters (e.g. internationalization):

type FiltersContext = { intl: IntlShape }

const { searchSchema, filters: filtersFn } = defineFilters<SearchQuery, FiltersContext>()(context => [
  nameFilter(context?.intl),
])

// In the component:
const intl = useIntl()
const filtersDefinition = useMemo(() => filtersFn({ intl }), [intl])

Implementing Custom Filter Definitions

This library does not ship filter UI components — you implement them in your application to match your design system. A filter definition is an object satisfying the FilterDefinition<TQuery, TValue, TId> type:

| Property | Type | Description | | ------------------ | -------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | | id | string | Unique identifier for the filter within the table | | component | ComponentType<FilterProps> | React component receiving { applyFilter, reset$, initialValue } | | searchSchema | [key, ZodType][] (optional) | URL param key/schema pairs for serialization | | buildApplyFilter | (value) => ((query) => query) \| undefined (optional) | Reconstructs the filter function from a stored value (needed for initial/restored values) | | toSearchParams | (value) => Record<string, unknown> (required when searchSchema is set) | Serializes the filter value to URL params | | fromSearchParams | (params) => value \| undefined (required when searchSchema is set) | Deserializes URL params back to a filter value |

Each filter component receives three props from useFilters:

  • applyFilter(filterFn, value) — call with a query transform function when the value changes, or undefined to clear
  • reset$ — an RxJS Observable that emits when filters are reset; subscribe to clear local state
  • initialValue — restored value when hydrating from URL params

Minimal Example

A simple text input filter showing the essential structure:

import { useState, useEffect, useCallback } from "react"
import z from "zod"
import { FilterDefinition } from "@leancodepl/antd-table-hooks"

const schema = z.string().optional().catch(undefined)

export function textFilter<TQuery, const TId extends string, const TTableId extends string>({
  id,
  tableId,
  label,
  filter,
}: {
  id: TId
  tableId: TTableId
  label: string
  filter: (value: string, query: TQuery) => TQuery
}) {
  const paramKey = `${tableId}-${id}` as const

  return {
    id,

    component: ({ applyFilter, reset$, initialValue }) => {
      const [value, setValue] = useState(initialValue)

      const clear = useCallback(() => {
        setValue(undefined)
        applyFilter(undefined)
      }, [applyFilter])

      useEffect(() => {
        const sub = reset$.subscribe(clear)
        return () => sub.unsubscribe()
      }, [clear, reset$])

      return (
        <input
          placeholder={label}
          value={value}
          onChange={e => {
            const v = e.target.value
            setValue(v)
            applyFilter(v ? query => filter(v, query) : undefined, v)
          }}
        />
      )
    },

    buildApplyFilter: value => (value ? query => filter(value, query) : undefined),

    searchSchema: [[paramKey, schema]] as const,
    toSearchParams: value => ({ [paramKey]: value }),
    fromSearchParams: params => params[paramKey] as string | undefined,
  } satisfies FilterDefinition<TQuery, string, TId>
}

Usage:

textFilter({
  id: "email",
  tableId: "users",
  label: "Email",
  filter: (email, query) => ({ ...query, EmailFilter: email }),
})

Key Implementation Patterns

Single URL param (text, select): use one searchSchema entry keyed as {tableId}-{id}.

searchSchema: [[`${tableId}-${id}`, z.string().optional()]] as const

Multiple URL params (date ranges): use multiple searchSchema entries with distinct keys.

searchSchema: [
  [`${tableId}-${id}-from`, z.string().optional()],
  [`${tableId}-${id}-to`, z.string().optional()],
] as const

Clearing: call applyFilter(undefined) when the filter value is empty or cleared. The reset$ observable fires when the user resets all filters — subscribe to it and clear local state.

Restoring from URL: fromSearchParams deserializes raw URL values back into the filter's value type. buildApplyFilter reconstructs the query transform function from a deserialized value, enabling filter restoration on page load.

Reusable Table with Query Params from Parent

Pass query params as props to share the same table component across multiple pages:

import { useSearch, useNavigate } from "@tanstack/react-router"
import z from "zod"

type UsersTableQueryParams = z.infer<typeof usersSearchSchema>

type UsersTableProps = {
  name: string
  queryParams: UsersTableQueryParams
  setQueryParams: (params: UsersTableQueryParams) => void
}

export function UsersTable({ name, queryParams, setQueryParams }: UsersTableProps) {
  const queryState = useTable({
    queryParams,
    setQueryParams,
    definedFilters: usersFilters,
    tableId: "users",
    defaultSortKey: SortKey.CreatedAt,
    defaultSortDirection: "descend",
  })

  // ... same pattern as full table example
}

// Parent page
export function UsersPage() {
  const search = useSearch({ from: "/users" })
  const navigate = useNavigate({ from: "/users" })

  return <UsersTable name="users" queryParams={search} setQueryParams={params => navigate({ search: params })} />
}