{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "use-search", "type": "registry:hook", "title": "Search Hook", "author": "Hitesh Agrawal", "description": "Type-safe search state management with URL synchronization and debouncing using nuqs", "dependencies": ["nuqs@^2.2.2"], "files": [ { "path": "registry/default/types/index.ts", "content": "export * from './common'\n", "type": "registry:lib", "target": "hooks/types/index.ts" }, { "path": "registry/default/types/common.ts", "content": "export type HistoryMode = 'push' | 'replace'\n\nexport interface BaseHookOptions {\n history?: HistoryMode\n scroll?: boolean\n shallow?: boolean\n}\n\nexport interface UseTransitionOptions {\n startTransition?: (callback: () => void) => void\n}\n", "type": "registry:lib", "target": "hooks/types/common.ts" }, { "path": "registry/default/utils/debounce.ts", "content": "import { useEffect, useState } from 'react'\n\nexport function useDebounce(value: T, delay: number): T {\n const [debouncedValue, setDebouncedValue] = useState(value)\n\n useEffect(() => {\n const handler = setTimeout(() => {\n setDebouncedValue(value)\n }, delay)\n\n return () => {\n clearTimeout(handler)\n }\n }, [value, delay])\n\n return debouncedValue\n}\n", "type": "registry:lib", "target": "lib/utils/debounce.ts" }, { "path": "registry/default/search/index.ts", "content": "export type { UseSearchOptions, UseSearchResult } from './types'\nexport { useSearch } from './useSearch'\n", "type": "registry:hook", "target": "hooks/search/index.ts" }, { "path": "registry/default/search/types.ts", "content": "import type { BaseHookOptions } from '../types'\n\nexport interface UseSearchOptions extends BaseHookOptions {\n defaultQuery?: string\n debounce?: number\n minLength?: number\n trim?: boolean\n queryKey?: string\n onSearch?: (query: string) => void\n}\n\nexport interface UseSearchResult {\n query: string\n debouncedQuery: string\n isSearching: boolean\n isDebouncing: boolean\n isValid: boolean\n setQuery: (query: string) => void\n clearQuery: () => void\n isPending: boolean\n}\n", "type": "registry:hook", "target": "hooks/search/types.ts" }, { "path": "registry/default/search/useSearch.ts", "content": "import { parseAsString, useQueryState } from 'nuqs'\nimport { useCallback, useEffect, useMemo } from 'react'\nimport { useDebounce } from '../../lib/utils/debounce'\nimport type { UseSearchOptions, UseSearchResult } from './types'\n\nexport function useSearch(options: UseSearchOptions = {}): UseSearchResult {\n const {\n defaultQuery = '',\n debounce = 300,\n minLength = 0,\n trim = true,\n queryKey = 'q',\n onSearch,\n history = 'replace',\n scroll = false,\n shallow = true,\n } = options\n\n const [rawQuery, setRawQuery] = useQueryState(\n queryKey,\n parseAsString.withDefault(defaultQuery).withOptions({\n history,\n scroll,\n shallow,\n })\n )\n\n const query = useMemo(() => {\n if (trim && rawQuery) return rawQuery.trim()\n return rawQuery ?? defaultQuery\n }, [rawQuery, trim, defaultQuery])\n\n const debouncedQuery = useDebounce(query, debounce)\n\n const isSearching = useMemo(\n () => query.length > 0 && query.length >= minLength,\n [query, minLength]\n )\n\n const isDebouncing = useMemo(() => query !== debouncedQuery, [query, debouncedQuery])\n\n const isValid = useMemo(() => query.length >= minLength, [query, minLength])\n\n const setQuery = useCallback(\n (newQuery: string) => {\n setRawQuery(newQuery)\n },\n [setRawQuery]\n )\n\n const clearQuery = useCallback(() => {\n setRawQuery(null)\n }, [setRawQuery])\n\n useEffect(() => {\n if (onSearch && isValid && !isDebouncing) {\n onSearch(debouncedQuery)\n }\n }, [onSearch, debouncedQuery, isValid, isDebouncing])\n\n return {\n query,\n debouncedQuery,\n isSearching,\n isDebouncing,\n isValid,\n setQuery,\n clearQuery,\n isPending: false,\n }\n}\n", "type": "registry:hook", "target": "hooks/search/useSearch.ts" } ] }