diff --git a/examples/react/basic-default-search-params/src/main.tsx b/examples/react/basic-default-search-params/src/main.tsx index b79a6398301..75ed3a6ebf2 100644 --- a/examples/react/basic-default-search-params/src/main.tsx +++ b/examples/react/basic-default-search-params/src/main.tsx @@ -172,12 +172,18 @@ function PostErrorComponent({ error }: ErrorRouteProps) { function PostComponent() { const post = postRoute.useLoaderData() - const { color } = postRoute.useSearch() + const { color, postId } = postRoute.useSearch() + const setSearch = postRoute.useSetSearch({ replace: false }) + return (

{post.title}


{post.body}
+ +
) } diff --git a/packages/react-router/src/index.tsx b/packages/react-router/src/index.tsx index 6841e31823f..899129eba61 100644 --- a/packages/react-router/src/index.tsx +++ b/packages/react-router/src/index.tsx @@ -26,4 +26,5 @@ export * from './useBlocker' export * from './useNavigate' export * from './useParams' export * from './useSearch' +export * from './useSetSearch' export * from './utils' diff --git a/packages/react-router/src/route.ts b/packages/react-router/src/route.ts index 3245b5c9519..ca9142fd7f1 100644 --- a/packages/react-router/src/route.ts +++ b/packages/react-router/src/route.ts @@ -9,6 +9,7 @@ import { RouteById, RouteIds, RoutePaths } from './routeInfo' import { AnyRouter, RegisteredRouter } from './router' import { useParams } from './useParams' import { useSearch } from './useSearch' +import { useSetSearch } from './useSetSearch' import { Assign, Expand, @@ -504,6 +505,12 @@ export class RouteApi< return useSearch({ ...opts, from: this.id } as any) } + useSetSearch = (opts?: { + replace?: boolean + }): ((search: Partial) => void) => { + return useSetSearch({ ...opts, from: this.id } as any) as any + } + useParams = (opts?: { select?: (s: TAllParams) => TSelected }): TSelected => { @@ -829,6 +836,12 @@ export class Route< return useSearch({ ...opts, from: this.id } as any) } + useSetSearch = (opts?: { + replace?: boolean + }): ((search: Partial) => void) => { + return useSetSearch({ ...opts, from: this.id } as any) as any + } + useParams = (opts?: { select?: (search: TAllParams) => TSelected }): TSelected => { diff --git a/packages/react-router/src/useSetSearch.tsx b/packages/react-router/src/useSetSearch.tsx new file mode 100644 index 00000000000..83fce4da9da --- /dev/null +++ b/packages/react-router/src/useSetSearch.tsx @@ -0,0 +1,44 @@ +import { AnyRoute } from './route' +import { RouteIds, RouteById } from './routeInfo' +import { RegisteredRouter } from './router' +import { RouteMatch } from './Matches' +import { useMatch } from './Matches' +import { useRouter } from './RouterProvider' +import React from 'react' + +export function useSetSearch< + TRouteTree extends AnyRoute = RegisteredRouter['routeTree'], + TFrom extends RouteIds = RouteIds, + TSearch extends Record = RouteById< + TRouteTree, + TFrom + >['types']['fullSearchSchema'], +>(opts: { + from: TFrom + replace: boolean | undefined +}): (search: Partial) => void { + const router = useRouter() + + const prevSearch = useMatch({ + from: opts.from, + select: (match: RouteMatch) => { + return match.search + }, + }) + + return React.useCallback((search: Partial) => { + const __tempSearch = { ...prevSearch, ...search } + const { hash, state, pathname } = router.state.location + const searchStr = router.options.stringifySearch(__tempSearch) + + router.commitLocation({ + replace: opts.replace ?? true, + pathname, + hash, + href: `${pathname}${searchStr}${hash}`, + search: __tempSearch, + searchStr, + state, + }) + }, []) +}