Hooks
React hooks for accessing router state and navigation.
useLocation()
Returns the current location object.
import { useLocation } from "@funstack/router";
function MyComponent() {
const location = useLocation();
console.log(location.pathname); // "/users/123"
console.log(location.search); // "?tab=profile"
console.log(location.hash); // "#section"
console.log(location.entryId); // NavigationHistoryEntry.id or null
console.log(location.entryKey); // NavigationHistoryEntry.key or null
}
entryId and entryKey are null when the Navigation API is unavailable. Do not render them directly in DOM — they are not available during SSR and will cause a hydration mismatch. Use them as a React key or in effects/callbacks instead.
useSearchParams()
Returns a tuple of the current search params and a setter function.
import { useSearchParams } from "@funstack/router";
function SearchPage() {
const [searchParams, setSearchParams] = useSearchParams();
const query = searchParams.get("q");
const handleSearch = (newQuery: string) => {
setSearchParams({ q: newQuery });
};
}
useBlocker(options)
Prevents navigation away from the current route. Useful for scenarios like unsaved form data, ongoing file uploads, or any state that would be lost on navigation.
import { useBlocker } from "@funstack/router";
import { useState, useCallback } from "react";
function EditForm() {
const [isDirty, setIsDirty] = useState(false);
useBlocker({
shouldBlock: useCallback(() => {
if (isDirty) {
return !confirm("You have unsaved changes. Leave anyway?");
}
return false;
}, [isDirty]),
});
const handleSave = () => {
// Save logic...
setIsDirty(false);
};
return (
<form>
<input onChange={() => setIsDirty(true)} />
<button type="button" onClick={handleSave}>
Save
</button>
</form>
);
}
Options
shouldBlock: A function that returns true to block navigation, or false to allow it. You can call confirm() inside this function to show a confirmation dialog. Wrap with useCallback when the function depends on state.
Notes
- Multiple blockers can coexist in the component tree. If any blocker returns
true, navigation is blocked. - This hook only handles SPA navigations (links, programmatic navigation). For hard navigations (tab close, refresh), handle
beforeunload separately.
useIsPending()
Returns whether a navigation transition is currently pending. When navigating to a route that suspends (e.g., using React.lazy), isPending becomes true while React keeps the previous UI visible, and returns to false once the new route is ready.
import { useIsPending } from "@funstack/router";
function MyComponent() {
const isPending = useIsPending();
return (
<div style={{ opacity: isPending ? 0.7 : 1 }}>
{isPending && <span>Navigating...</span>}
{/* page content */}
</div>
);
}
Return Value
boolean — true when a navigation transition is in progress (the destination route is suspending), false otherwise.
Notes
- This hook is powered by React's
useTransition. The router wraps navigations in startTransition, so React defers rendering suspended routes and keeps the current UI visible. - Sync state updates via
setStateSync and resetStateSync bypass transitions entirely, so isPending will not become true for those updates. - The same
isPending value is also available as a prop on route components.
Type-Safe Hooks
These hooks provide type-safe access to route data when using routes defined with an id. They extract type information from TypefulOpaqueRouteDefinition and validate at runtime that the specified route exists in the current route hierarchy.
In nested routes, these hooks can access data from any ancestor route in the hierarchy. For example, a child route component can use useRouteParams(parentRoute) to access the parent route's parameters.
useRouteParams(route)
Returns typed route parameters for the given route definition. The parameter types are automatically inferred from the route's path pattern.
import { route, useRouteParams } from "@funstack/router";
// Define route with id for type-safe access
const userRoute = route({
id: "user",
path: "/users/:userId",
component: UserPage,
});
function UserPage() {
// params is typed as { userId: string }
const params = useRouteParams(userRoute);
return <div>User ID: {params.userId}</div>;
}
// In nested routes, access parent route params:
const orgRoute = route({
id: "org",
path: "/org/:orgId",
component: OrgLayout,
children: [teamRoute],
});
function TeamPage() {
// Access parent route's params
const { orgId } = useRouteParams(orgRoute);
return <div>Org: {orgId}</div>;
}
Errors
- Throws if called outside a route component (no RouteContext).
- Throws if the specified route's
id is not found in the current route hierarchy (neither the current route nor any ancestor).
useRouteState(route)
Returns typed navigation state for the given route definition. Use this with routes defined via routeState<T>() to get properly typed state.
import { routeState, useRouteState } from "@funstack/router";
type ScrollState = { scrollPos: number };
const scrollRoute = routeState<ScrollState>()({
id: "scroll",
path: "/page",
component: ScrollPage,
});
function ScrollPage() {
// state is typed as ScrollState | undefined
const state = useRouteState(scrollRoute);
return <div>Scroll position: {state?.scrollPos ?? 0}</div>;
}
Return Value
Returns State | undefined. State is undefined on initial visit and when navigating to a new entry without state.
Errors
- Throws if called outside a route component (no RouteContext).
- Throws if the specified route's
id is not found in the current route hierarchy (neither the current route nor any ancestor).
useRouteData(route)
Returns typed loader data for the given route definition. The data type is automatically inferred from the route's loader function.
import { route, useRouteData } from "@funstack/router";
const userRoute = route({
id: "user",
path: "/users/:userId",
loader: async ({ params }) => {
const res = await fetch(`/api/users/${params.userId}`);
return res.json() as Promise<{ name: string; age: number }>;
},
component: UserPage,
});
function UserPage() {
// data is typed as Promise<{ name: string; age: number }>
const data = useRouteData(userRoute);
// Use with React.use() or Suspense
const user = use(data);
return <div>User: {user.name}</div>;
}
Errors
- Throws if called outside a route component (no RouteContext).
- Throws if the specified route's
id is not found in the current route hierarchy (neither the current route nor any ancestor).