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 🙏

© 2025 – Pkg Stats / Ryan Hefner

nuqs-presets

v0.1.3

Published

High-level pattern hooks for nuqs - pagination, filtering, sorting, and more

Readme

nuqs-presets

High-level pattern hooks for nuqs - Stop reinventing pagination, filtering, sorting, and search.

NPM Version License TypeScript

Why?

nuqs is an excellent library for managing URL state, but building common patterns like pagination, filtering, and sorting still requires boilerplate. nuqs-presets provides ready-to-use hooks that solve these patterns with best practices baked in.

Before

// 30+ lines of boilerplate for pagination alone
const [page, setPage] = useQueryState('page', parseAsInteger.withDefault(1))
const [pageSize, setPageSize] = useQueryState('pageSize', parseAsInteger.withDefault(10))
// ... handle navigation, validation, edge cases ...

After

// 2 lines, everything handled
const { page, pageSize, nextPage, prevPage, hasNextPage } = usePagination()

Features

  • 7 production-ready hooks - Pagination, filtering, sorting, search, tabs, date ranges, multi-select
  • Type-safe - Full TypeScript support with excellent inference
  • Zero config - Sensible defaults for immediate use
  • Customizable - Override any behavior when needed
  • Tiny - Tree-shakeable, minimal bundle size
  • Framework agnostic - Works anywhere nuqs works (Next.js, Remix, React Router, etc.)

Installation

npm install nuqs-presets nuqs
# or
pnpm add nuqs-presets nuqs
# or
yarn add nuqs-presets nuqs
# or
bun add nuqs-presets nuqs

Requirements:

  • nuqs ^2.0.0
  • react ^18.3.0 or ^19.0.0

Quick Start

1. Set up nuqs adapter

Follow the nuqs setup guide for your framework:

// Next.js App Router - app/layout.tsx
import { NuqsAdapter } from 'nuqs/adapters/next/app'

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <NuqsAdapter>{children}</NuqsAdapter>
      </body>
    </html>
  )
}

2. Use presets in your components

'use client'

import { usePagination, useFilters, useSorting } from 'nuqs-presets'
import { parseAsString, parseAsFloat } from 'nuqs'

const filterParsers = {
  category: parseAsString,
  minPrice: parseAsFloat,
}

export function ProductList() {
  const { page, pageSize, nextPage, prevPage, hasNextPage, hasPrevPage } = usePagination()

  const { filters, setFilter, clearFilters } = useFilters({
    parsers: filterParsers,
  })

  const { sortBy, sortOrder, toggleSort } = useSorting({
    columns: ['name', 'price', 'date'] as const
  })

  return (
    <div>
      {/* Your UI */}
      <button onClick={prevPage} disabled={!hasPrevPage}>Previous</button>
      <span>Page {page}</span>
      <button onClick={nextPage} disabled={!hasNextPage}>Next</button>
    </div>
  )
}

Hooks

usePagination

Complete pagination with all the bells and whistles.

const {
  page,          // Current page (1-indexed)
  pageSize,      // Items per page
  totalPages,    // Total pages (computed)
  hasNextPage,   // Can go forward
  hasPrevPage,   // Can go back
  nextPage,      // Go to next page
  prevPage,      // Go to previous page
  goToPage,      // Go to specific page
  setPageSize,   // Change page size
} = usePagination({
  defaultPageSize: 10,
  totalItems: 1000,
})

useFilters

Type-safe filter management with nuqs parsers.

import { parseAsString, parseAsFloat } from 'nuqs'

const filterParsers = {
  category: parseAsString,
  minPrice: parseAsFloat,
  maxPrice: parseAsFloat,
  inStock: parseAsBoolean,
}

const {
  filters,       // Current filters (type-safe)
  setFilter,     // Set a single filter
  clearFilters,  // Clear all filters
  hasFilters,    // Any filters active?
} = useFilters({
  parsers: filterParsers,
})

// filters.category is string | null
// filters.minPrice is number | null

useSorting

Smart column sorting with toggle behavior.

const {
  sortBy,        // Current sort column
  sortOrder,     // 'asc' | 'desc' | null
  toggleSort,    // Toggle column (null → asc → desc → null)
  isSortedBy,    // Check if column is sorted
} = useSorting({
  columns: ['name', 'date', 'price'] as const
})

useSearch

Debounced search with min length validation.

const {
  query,           // Current search query
  debouncedQuery,  // Debounced value for API calls
  setQuery,        // Update search
  isDebouncing,    // Debounce in progress
} = useSearch({
  debounce: 300,
  minLength: 2,
})

useTabs

Type-safe tab navigation.

const {
  activeTab,     // Current tab (type-safe)
  setTab,        // Change tab
  isActive,      // Check if tab is active
} = useTabs(['overview', 'analytics', 'settings'] as const)

useDateRange

Date range selection with presets.

const {
  startDate,     // Start date
  endDate,       // End date
  setRange,      // Set both dates
  presets,       // Quick presets (last 7 days, etc.)
} = useDateRange({
  defaultPreset: 'last7days'
})

useMultiSelect

Array-based multi-selection.

const {
  selected,      // Selected items
  toggle,        // Toggle selection
  selectAll,     // Select all
  deselectAll,   // Deselect all
} = useMultiSelect({
  allItems: ['item1', 'item2', 'item3']
})

API Reference

For detailed API documentation for each hook, see the source code or TypeScript definitions. All hooks are fully typed with JSDoc comments.

Live Examples

This repository includes working example applications in the examples/ directory. These are complete, runnable apps that demonstrate real-world usage.

🚀 Available Examples

nextjs-basic - ✅ Complete

A simple Next.js 16 app demonstrating basic usage with:

  • Pagination with page size control
  • Debounced search
  • Multi-column sorting
  • Clean, modern UI with dark mode support

Run it:

cd examples/nextjs-basic
npm run dev

Open http://localhost:3000

nextjs-ecommerce - 🧪 Beta

Advanced e-commerce filtering interface with:

  • Multi-faceted filtering (category, price range, brand)
  • Filter badges with clear functionality
  • Type-safe parsers with nuqs

Run it:

cd examples/nextjs-ecommerce
npm run dev

Open http://localhost:3000

nextjs-dashboard - 🧪 Beta

Admin dashboard demonstrating:

  • Tab-based navigation
  • Data tables with all features
  • Date range filtering

Run it:

cd examples/nextjs-dashboard
npm run dev

Open http://localhost:3000

react-vite - 📦 Coming Soon

Framework-agnostic React SPA with:

  • Vite for fast development
  • React Router integration
  • Client-side routing

See the examples README for more details.

Code Examples

E-commerce Product List

'use client'

import { usePagination, useFilters, useSorting, useSearch } from 'nuqs-presets'
import { parseAsString, parseAsFloat, parseAsBoolean } from 'nuqs'

const filterParsers = {
  category: parseAsString,
  minPrice: parseAsFloat,
  maxPrice: parseAsFloat,
  inStock: parseAsBoolean,
}

export function ProductList() {
  const { page, pageSize, setPage, hasNextPage, hasPrevPage } = usePagination({
    defaultPageSize: 24,
    totalItems: 1000,
  })

  const { filters, setFilter, clearFilters, hasFilters } = useFilters({
    parsers: filterParsers,
  })

  const { sortBy, sortOrder, toggleSort } = useSorting({
    columns: ['name', 'price', 'rating'] as const,
    defaultColumn: 'name',
    defaultOrder: 'asc',
  })

  const { query, debouncedQuery, setQuery } = useSearch({
    debounce: 300,
    minLength: 2,
  })

  // Fetch products with all filters
  const { data: products } = useProducts({
    page,
    pageSize,
    ...filters,
    sortBy,
    sortOrder,
    search: debouncedQuery,
  })

  return (
    <div>
      <input
        type="text"
        value={query}
        onChange={(e) => setQuery(e.target.value)}
        placeholder="Search products..."
      />

      <div>
        <select onChange={(e) => setFilter('category', e.target.value)}>
          <option value="">All Categories</option>
          <option value="electronics">Electronics</option>
          <option value="books">Books</option>
          <option value="clothing">Clothing</option>
        </select>

        <button onClick={clearFilters} disabled={!hasFilters}>
          Clear Filters
        </button>
      </div>

      <div>
        <button onClick={() => toggleSort('name')}>
          Name {sortBy === 'name' && (sortOrder === 'asc' ? '↑' : '↓')}
        </button>
        <button onClick={() => toggleSort('price')}>
          Price {sortBy === 'price' && (sortOrder === 'asc' ? '↑' : '↓')}
        </button>
      </div>

      <div>
        {products?.map((product) => (
          <div key={product.id}>{product.name}</div>
        ))}
      </div>

      <div>
        <button onClick={() => setPage(page - 1)} disabled={!hasPrevPage}>
          Previous
        </button>
        <span>Page {page}</span>
        <button onClick={() => setPage(page + 1)} disabled={!hasNextPage}>
          Next
        </button>
      </div>
    </div>
  )
}

Tree-shaking

All hooks are tree-shakeable. Import only what you need:

// Import individual hooks
import { usePagination } from 'nuqs-presets/pagination'
import { useFilters } from 'nuqs-presets/filtering'
import { useSorting } from 'nuqs-presets/sorting'

TypeScript

All hooks are fully typed with excellent type inference. No need to manually specify types in most cases:

const { activeTab } = useTabs(['overview', 'analytics', 'settings'] as const)
// activeTab is typed as 'overview' | 'analytics' | 'settings'

const filterParsers = {
  category: parseAsString,
  minPrice: parseAsFloat,
}

const { filters } = useFilters({
  parsers: filterParsers,
})
// filters.category is typed as string | null
// filters.minPrice is typed as number | null

Contributing

Contributions are welcome! Please read our Contributing Guide first.

License

MIT © Hitesh Agrawal

Credits

Built on top of the excellent nuqs by François Best.


⭐ If you find this useful, please star the repo!