react-select-async-paginate

React Select wrapper that adds pagination-on-scroll to async, remote-loaded option lists

Library
npm
v0.7.11
331stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
46/100Fair
Development Activity8
Maintenance20
Community76
Maturity60
Momentum20

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
70/100Good
Architecture80
Code Quality78
Innovation75
Learning Curve45

react-select-async-paginate wraps react-select’s Select, Async, and Creatable variants with hooks and a higher-order component that load menu options page by page as the user scrolls, types, or opens the menu. Instead of hand-rolling debounce timers, per-search caches, and “has more” bookkeeping around react-select’s own Async loader, consumers implement a single loadOptions(inputValue, loadedOptions, additional) function and the library manages request sequencing, caching per search string, and result concatenation.

The package is deliberately small in scope: it exposes a drop-in AsyncPaginate component for the common case, plus useAsyncPaginate/useAsyncPaginateBase hooks and a withAsyncPaginate HOC for wrapping custom or third-party Select components (e.g. Creatable) with the same pagination behavior. Options are cached by the current search string so switching between an empty search and a typed query doesn’t refetch already-loaded pages, and helpers like reduceGroupedOptions support paginating grouped option lists.

What You Get

  • AsyncPaginate — a drop-in react-select replacement that paginates options as the menu scrolls
  • withAsyncPaginate HOC to add the same pagination behavior to Creatable or any other custom Select component
  • useAsyncPaginate / useAsyncPaginateBase hooks for building fully custom select UIs on top of the same loading/caching logic
  • Per-search-string options cache that avoids re-fetching pages already loaded for a given query
  • Configurable debounce (debounceTimeout), retry-after-error timeout (reloadOnErrorTimeout), and cache invalidation (clearCacheOnSearchChange, clearCacheOnMenuClose)
  • reduceGroupedOptions helper for paginating grouped (labelled) option lists

Common Use Cases

  • Populating a select input from a paginated REST or GraphQL endpoint (offset- or page-based) without loading the whole dataset upfront
  • Type-ahead search-as-you-type against a remote API with built-in debouncing
  • Large reference-data pickers (users, organizations, products) where the full list is too big to load at once
  • Adding “load more on scroll” behavior to a Creatable select that also lets users add new options
  • Building a fully custom select UI that still needs react-select-async-paginate’s caching/pagination semantics via its hooks

Under The Hood

Architecture The library is organized as small, individually unit-tested pure functions (defaultReduceOptions, defaultShouldLoadMore, getInitialCache, getInitialOptionsCache, validateResponse) composed by two layered hooks: useAsyncPaginateBase (in src/useAsyncPaginateBase.ts) owns a ref-backed per-search-string options cache and drives loading via requestOptions (src/requestOptions.ts), while useAsyncPaginate layers react-select’s inputValue/menuIsOpen state on top. withAsyncPaginate (src/withAsyncPaginate.tsx) is a generic HOC that injects the hook’s resulting props into any react-select component, which is how the exported AsyncPaginate (withAsyncPaginate(Select)) is built. This split means the core scroll/cache/debounce logic has exactly one implementation shared by the default component, the Creatable wrapping path, and fully custom consumers using the hooks directly — changing requestOptions.ts changes behavior everywhere at once. Tech Stack Written in TypeScript against react-select ^5 as a peer dependency, with a handful of small focused dependencies: @vtaits/use-lazy-ref and use-latest for ref-stable callbacks across renders, use-is-mounted-ref to guard against post-unmount state updates, krustykrab for a Result-style wrapper around the async loadOptions call, and sleep-promise for debounce delays. The package builds with tsup to dual ESM/CJS output plus .d.ts typings, and the repo is a Bun-managed monorepo (this package plus react-select-fetch) using Biome for linting/formatting. Code Quality Every pure helper module has a matching *.test.ts file run under Vitest (including a browser-mode config for requestOptions/index integration tests), giving comprehensive coverage of the caching, reduction, and validation logic specifically, though there is comparatively less test coverage of the top-level hooks’ render-cycle behavior itself. Error handling is explicit via krustykrab’s Result type (isErr()/unwrap()) rather than bare try/catch, and tsc --noEmit plus Biome enforce typing and style. No GitHub Actions CI configuration is present in the current repo snapshot (a legacy .travis.yml remains from an earlier setup), though a Codecov badge suggests CI runs are wired up elsewhere. API Design The public surface is intentionally minimal — a consumer only has to supply loadOptions, and everything else (debounce, hasMore tracking, per-search caching) has sensible defaults. The same underlying logic is exposed at three levels of control — the ready-made AsyncPaginate component, the withAsyncPaginate HOC for wrapping other Select variants, and the raw useAsyncPaginate/useAsyncPaginateBase hooks for fully custom UIs — so consumers can opt into more control without reimplementing pagination semantics themselves.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search