Repository: TanStack/router
Pull request #1577 (2024-05-09 21:51:32)

feat(react-router): add a `useLocation` hook to get the current `ParsedLocation`
The `useLocation` is a useful hook in [_React Router_](https://reactrouter.com/en/main/hooks/use-location).
So I decided to add it to the _TanStack Router_ too.

SeanCassiere: Not sure if we are going to add this hook into the library or not, but this PR would need some changes should we decide to add it in.
nx-cloud[bot]: # ☁️ Nx Cloud Report

CI is running for commit 9389e846e78abb15b20fa39f219aac68344bf2c4.

[📂 Click to track the progress, see the status, the terminal output, and the build insights.](https://cloud.nx.app/cipes/663ddc4f3deb0631e2123b8c?utm_source=pull-request&utm_medium=comment)


---

Sent with 💌 from [NxCloud.](https://nx.app?utm_source=pull-request&utm_medium=comment)
SeanCassiere: Good to go.
MrPancakes39: I apologize for not seeing the needed changes sooner and resolving them myself.
Thank you @SeanCassiere for your consideration and work.

Patch:
diff --git a/docs/framework/react/api/router.md b/docs/framework/react/api/router.md
index 6f2dc228c..8e9f42beb 100644
--- a/docs/framework/react/api/router.md
+++ b/docs/framework/react/api/router.md
@@ -35,6 +35,7 @@ title: Router API
   - [`useLinkProps()`](./useLinkPropsHook)
   - [`useLoaderData()`](./useLoaderDataHook)
   - [`useLoaderDeps()`](./useLoaderDepsHook)
+  - [`useLocation()`](./useLocationHook)
   - [`useMatch()`](./useMatchHook)
   - [`useMatchRoute()`](./useMatchRouteHook)
   - [`useMatches()`](./useMatchesHook)
diff --git a/docs/framework/react/api/router/useLocationHook.md b/docs/framework/react/api/router/useLocationHook.md
new file mode 100644
index 000000000..a857babe3
--- /dev/null
+++ b/docs/framework/react/api/router/useLocationHook.md
@@ -0,0 +1,40 @@
+---
+id: useLocationHook
+title: useLocation hook
+---
+
+The `useLocation` method is a hook that returns the current [`location`](../ParsedLocationType) object. This hook is useful for when you want to perform some side effect whenever the current location changes.
+
+## useLocation options
+
+The `useLocation` hook accepts an optional `options` object.
+
+### `opts.select` option
+
+- Type: `(state: ParsedLocationType) => TSelected`
+- Optional
+- If supplied, this function will be called with the [`location`](../ParsedLocationType) object and the return value will be returned from `useLocation`.
+
+## useLocation returns
+
+- The current [`location`](../ParsedLocationType) object or `TSelected` if a `select` function is provided.
+
+## Examples
+
+```tsx
+import { useLocation } from '@tanstack/react-router'
+
+function Component() {
+  const location = useLocation()
+  //    ^ ParsedLocation
+
+  // OR
+
+  const pathname = useLocation({
+    select: (location) => location.pathname,
+  })
+  //    ^ string
+
+  // ...
+}
+```
diff --git a/packages/react-router/src/index.tsx b/packages/react-router/src/index.tsx
index 2e647a257..0257eebee 100644
--- a/packages/react-router/src/index.tsx
+++ b/packages/react-router/src/index.tsx
@@ -258,6 +258,7 @@ export {
 export { useRouteContext } from './useRouteContext'
 export { useRouter } from './useRouter'
 export { useRouterState } from './useRouterState'
+export { useLocation } from './useLocation'
 export {
   escapeJSON, // SSR
   useLayoutEffect, // SSR
diff --git a/packages/react-router/src/useLocation.tsx b/packages/react-router/src/useLocation.tsx
new file mode 100644
index 000000000..6da738533
--- /dev/null
+++ b/packages/react-router/src/useLocation.tsx
@@ -0,0 +1,15 @@
+import { useRouterState } from './useRouterState'
+import type { AnyRouter, RegisteredRouter, RouterState } from './router'
+
+export function useLocation<
+  TRouter extends AnyRouter = RegisteredRouter,
+  TLocationState = RouterState<TRouter['routeTree']>['location'],
+  TSelected = TLocationState,
+>(opts?: { select?: (state: TLocationState) => TSelected }): TSelected {
+  return useRouterState({
+    select: (state) =>
+      opts?.select
+        ? opts.select(state.location as TLocationState)
+        : (state.location as TSelected),
+  })
+}


Test patch:
diff --git a/packages/react-router/tests/useLocation.test-d.tsx b/packages/react-router/tests/useLocation.test-d.tsx
new file mode 100644
index 000000000..8643f3a60
--- /dev/null
+++ b/packages/react-router/tests/useLocation.test-d.tsx
@@ -0,0 +1,48 @@
+import { expectTypeOf, test } from 'vitest'
+import {
+  createRootRoute,
+  createRoute,
+  createRouter,
+  useLocation,
+  type ParsedLocation,
+  type RouterState,
+} from '../src'
+
+const rootRoute = createRootRoute()
+
+const indexRoute = createRoute({
+  getParentRoute: () => rootRoute,
+  path: '/',
+})
+
+const invoicesRoute = createRoute({
+  getParentRoute: () => rootRoute,
+  path: 'invoices',
+})
+
+const routeTree = rootRoute.addChildren([invoicesRoute, indexRoute])
+
+const defaultRouter = createRouter({ routeTree })
+
+type DefaultRouter = typeof defaultRouter
+
+test('should have the types for a ParsedLocation in useLocation', () => {
+  const location = useLocation<DefaultRouter>()
+
+  expectTypeOf(location).toEqualTypeOf<ParsedLocation>()
+  expectTypeOf(location)
+    .toHaveProperty('pathname')
+    .toEqualTypeOf<ParsedLocation['pathname']>()
+})
+
+test('should have the type of string for selecting the pathname in useLocation', () => {
+  const pathname = useLocation<
+    DefaultRouter,
+    RouterState<DefaultRouter['routeTree']>['location'],
+    RouterState<DefaultRouter['routeTree']>['location']['pathname']
+  >({
+    select: (state) => state.pathname,
+  })
+
+  expectTypeOf(pathname).toMatchTypeOf<ParsedLocation['pathname']>()
+})