diff --git a/e2e/react-router/basic/src/main.tsx b/e2e/react-router/basic/src/main.tsx
index 16eda46d5c..756686d634 100644
--- a/e2e/react-router/basic/src/main.tsx
+++ b/e2e/react-router/basic/src/main.tsx
@@ -7,6 +7,7 @@ import {
createRootRoute,
createRoute,
createRouter,
+ useNavigate,
} from '@tanstack/react-router'
import { TanStackRouterDevtools } from '@tanstack/router-devtools'
import { NotFoundError, fetchPost, fetchPosts } from './posts'
@@ -59,6 +60,15 @@ function RootComponent() {
>
Layout
{' '}
+
+ Search Param Binding
+ {' '}
I'm layout B!
}
+const searchParamBindingRoute = createRoute({
+ getParentRoute: () => rootRoute,
+ path: '/search-param-binding',
+ component: SearchParamBindingComponent,
+ validateSearch: (input): { filter?: string } => {
+ return {
+ filter: typeof input.filter === 'string' ? input.filter : undefined,
+ }
+ },
+})
+
+function SearchParamBindingComponent() {
+ const navigate = useNavigate()
+ const { filter } = searchParamBindingRoute.useSearch()
+
+ return (
+
+
+ navigate({
+ to: '.',
+ search: { filter: e.target.value },
+ })
+ }
+ />
+
+ )
+}
+
const routeTree = rootRoute.addChildren([
postsRoute.addChildren([postRoute, postsIndexRoute]),
layoutRoute.addChildren([
layout2Route.addChildren([layoutARoute, layoutBRoute]),
]),
+ searchParamBindingRoute,
indexRoute,
])
diff --git a/e2e/react-router/basic/tests/app.spec.ts b/e2e/react-router/basic/tests/app.spec.ts
index 5ccdafa513..6c6a069a8b 100644
--- a/e2e/react-router/basic/tests/app.spec.ts
+++ b/e2e/react-router/basic/tests/app.spec.ts
@@ -45,3 +45,33 @@ test('Navigating to a post page with viewTransition types', async ({
await page.getByRole('link', { name: 'sunt aut facere repe' }).click()
await expect(page.getByRole('heading')).toContainText('sunt aut facere')
})
+
+test('#3162 - Binding an input to search params with stable cursor position', async ({
+ page,
+}) => {
+ await page
+ .getByRole('link', { name: 'Search Param Binding', exact: true })
+ .click()
+ expect(page).toHaveURL(/.*\/search-param-binding/)
+
+ await page.getByTestId('filter').fill('Hello World')
+ expect(page.getByTestId('filter')).toHaveValue('Hello World')
+ expect(page).toHaveURL(/.*\/search-param-binding\?filter=Hello%20World/)
+
+ await page.getByTestId('filter').click()
+ for (let i = 0; i < 5; i++) {
+ await page.keyboard.press('ArrowLeft')
+ }
+ await page.keyboard.press('Space')
+ await page.keyboard.press('H')
+ await page.keyboard.press('A')
+ await page.keyboard.press('P')
+ await page.keyboard.press('P')
+ await page.keyboard.press('Y')
+ await page.getByTestId('filter').blur()
+
+ expect(page.getByTestId('filter')).toHaveValue('Hello Happy World')
+ expect(page).toHaveURL(
+ /.*\/search-param-binding\?filter=Hello%20Happy%20World/,
+ )
+})