T
TanStack5mo ago
manual-pink

useSearchParam custom hook: looking for advice

I'm working on a custom hook useSearchParam which would operate like a useState, but update the search params on change with optional debounce. I'm looking for the right way to use the built in type-safety for searchParams. But I can't seem to find any way to make it work by passing the Route, RouteContext, searchParams object or anything else from the Route without running into absolute Typescript nightmares. Current solution: use the inferred type from my Zod schema that's already validating searchParams as a generic that's passed to the hook. It works, but it feels like I'm not using the router as intended. Is there a better way to do this? And more generally @Tanner Linsley - what's the right way to pass type-safe properties of a Route (like searchParams) to a custom hook that will be reused with many routes?
import { useRouter } from '@tanstack/react-router'
import { useEffect, useState } from 'react'

type UseSearchParamProps<ValueType, SearchParamsType> = {
key: keyof SearchParamsType
initial_value?: ValueType
debounce_time?: number
}

export function useSearchParam<ValueType, SearchParamsType>({
key,
initial_value,
debounce_time = 0
}: UseSearchParamProps<ValueType, SearchParamsType>) {
const [value, setValue] = useState<ValueType | undefined>(initial_value)
const { navigate } = useRouter()

useEffect(() => {
const debounceTimer = setTimeout(() => {
navigate({
replace: true,
to: '.',
search: prev => ({ ...prev, [key]: value || undefined })
})
}, debounce_time)

return () => clearTimeout(debounceTimer)
}, [value, debounce_time, navigate, key])

return [value, setValue] as const
}
import { useRouter } from '@tanstack/react-router'
import { useEffect, useState } from 'react'

type UseSearchParamProps<ValueType, SearchParamsType> = {
key: keyof SearchParamsType
initial_value?: ValueType
debounce_time?: number
}

export function useSearchParam<ValueType, SearchParamsType>({
key,
initial_value,
debounce_time = 0
}: UseSearchParamProps<ValueType, SearchParamsType>) {
const [value, setValue] = useState<ValueType | undefined>(initial_value)
const { navigate } = useRouter()

useEffect(() => {
const debounceTimer = setTimeout(() => {
navigate({
replace: true,
to: '.',
search: prev => ({ ...prev, [key]: value || undefined })
})
}, debounce_time)

return () => clearTimeout(debounceTimer)
}, [value, debounce_time, navigate, key])

return [value, setValue] as const
}
2 Replies
manual-pink
manual-pinkOP5mo ago
As a note: technically using the navigate() function as I am with "." as the "to" value allows me to add any searchParam, even outside of the validated schema in the route definition. I'm using the "keyof SearchParamsType" to mitigate that, but it leads me to think I'm working outside the system
fascinating-indigo
fascinating-indigo5mo ago
Type Utilities | TanStack Router React Docs
Most types exposed by TanStack Router are internal, subject to breaking changes and not always easy to use. That is why TanStack Router has a subset of exposed types focused on ease of use with the in...

Did you find this page helpful?