Created
          September 24, 2025 12:42 
        
      - 
      
 - 
        
Save tarasyarema/b0f54777a4006a820d85dd9d0b42bd95 to your computer and use it in GitHub Desktop.  
    React - useTypedQueryParams
  
        
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
  | import { useCallback, useMemo } from "react"; | |
| import { useSearchParams } from "react-router"; | |
| export type QueryParamsMap = Record<string, string | undefined>; | |
| export interface UseQueryParamsReturn { | |
| /** | |
| * All query parameters as a key-value map | |
| */ | |
| params: QueryParamsMap; | |
| /** | |
| * Get a specific query parameter value | |
| */ | |
| getParam: (key: string) => string | undefined; | |
| /** | |
| * Set a single query parameter | |
| */ | |
| setParam: (key: string, value: string | undefined) => void; | |
| /** | |
| * Set multiple query parameters at once | |
| */ | |
| setParams: (updates: QueryParamsMap) => void; | |
| /** | |
| * Remove a query parameter | |
| */ | |
| removeParam: (key: string) => void; | |
| /** | |
| * Remove multiple query parameters | |
| */ | |
| removeParams: (keys: string[]) => void; | |
| /** | |
| * Clear all query parameters | |
| */ | |
| clearParams: () => void; | |
| /** | |
| * Check if a query parameter exists | |
| */ | |
| hasParam: (key: string) => boolean; | |
| } | |
| /** | |
| * Hook for managing URL query parameters | |
| * | |
| * @example | |
| * ```tsx | |
| * function MyComponent() { | |
| * const { params, setParam, getParam } = useQueryParams(); | |
| * | |
| * const currentTab = getParam('tab') ?? 'details'; | |
| * | |
| * const handleTabChange = (tab: string) => { | |
| * setParam('tab', tab); | |
| * }; | |
| * | |
| * return ( | |
| * <div> | |
| * <p>Current params: {JSON.stringify(params)}</p> | |
| * <button onClick={() => handleTabChange('issues')}> | |
| * Switch to Issues | |
| * </button> | |
| * </div> | |
| * ); | |
| * } | |
| * ``` | |
| */ | |
| export function useQueryParams(): UseQueryParamsReturn { | |
| const [searchParams, setSearchParams] = useSearchParams(); | |
| // Convert URLSearchParams to a plain object | |
| const params = useMemo<QueryParamsMap>(() => { | |
| const result: QueryParamsMap = {}; | |
| searchParams.forEach((value, key) => { | |
| result[key] = value; | |
| }); | |
| return result; | |
| }, [searchParams]); | |
| const getParam = useCallback( | |
| (key: string): string | undefined => { | |
| return searchParams.get(key) ?? undefined; | |
| }, | |
| [searchParams], | |
| ); | |
| const setParam = useCallback( | |
| (key: string, value: string | undefined) => { | |
| setSearchParams((prev) => { | |
| const newParams = new URLSearchParams(prev); | |
| if (value === undefined || value === null || value === "") { | |
| newParams.delete(key); | |
| } else { | |
| newParams.set(key, value); | |
| } | |
| return newParams; | |
| }); | |
| }, | |
| [setSearchParams], | |
| ); | |
| const setParams = useCallback( | |
| (updates: QueryParamsMap) => { | |
| setSearchParams((prev) => { | |
| const newParams = new URLSearchParams(prev); | |
| Object.entries(updates).forEach(([key, value]) => { | |
| if (value === undefined || value === null || value === "") { | |
| newParams.delete(key); | |
| } else { | |
| newParams.set(key, value); | |
| } | |
| }); | |
| return newParams; | |
| }); | |
| }, | |
| [setSearchParams], | |
| ); | |
| const removeParam = useCallback( | |
| (key: string) => { | |
| setSearchParams((prev) => { | |
| const newParams = new URLSearchParams(prev); | |
| newParams.delete(key); | |
| return newParams; | |
| }); | |
| }, | |
| [setSearchParams], | |
| ); | |
| const removeParams = useCallback( | |
| (keys: string[]) => { | |
| setSearchParams((prev) => { | |
| const newParams = new URLSearchParams(prev); | |
| keys.forEach((key) => newParams.delete(key)); | |
| return newParams; | |
| }); | |
| }, | |
| [setSearchParams], | |
| ); | |
| const clearParams = useCallback(() => { | |
| setSearchParams({}); | |
| }, [setSearchParams]); | |
| const hasParam = useCallback( | |
| (key: string): boolean => { | |
| return searchParams.has(key); | |
| }, | |
| [searchParams], | |
| ); | |
| return { | |
| params, | |
| getParam, | |
| setParam, | |
| setParams, | |
| removeParam, | |
| removeParams, | |
| clearParams, | |
| hasParam, | |
| }; | |
| } | |
| /** | |
| * Typed version of useQueryParams for better type safety | |
| * | |
| * @example | |
| * ```tsx | |
| * interface MyParams { | |
| * tab?: 'details' | 'issues'; | |
| * page?: string; | |
| * filter?: string; | |
| * } | |
| * | |
| * function MyComponent() { | |
| * const { params, setParam } = useTypedQueryParams<MyParams>(); | |
| * | |
| * const currentTab = params.tab ?? 'details'; | |
| * | |
| * return ( | |
| * <div> | |
| * <button onClick={() => setParam('tab', 'issues')}> | |
| * Issues Tab | |
| * </button> | |
| * </div> | |
| * ); | |
| * } | |
| * ``` | |
| */ | |
| export function useTypedQueryParams<T = QueryParamsMap>(): Omit< | |
| UseQueryParamsReturn, | |
| "params" | |
| > & { params: T } { | |
| const queryParams = useQueryParams(); | |
| return { | |
| ...queryParams, | |
| params: queryParams.params as T, | |
| }; | |
| } | 
  
    Sign up for free
    to join this conversation on GitHub.
    Already have an account?
    Sign in to comment