React bindings for type-router - A lightweight, type-safe router with first-class TypeScript support. Zero dependencies beyond React, full type inference, and a simple but powerful API designed for React applications.
- 🎯 100% Type-Safe: Full TypeScript support with automatic parameter type inference
- ⚛️ React Integration: First-class React hooks, components, and patterns
- 🪶 Lightweight: Zero dependencies beyond React, ~3KB minified
- 🔄 Flexible Routing: Support for both hash-based and history-based routing
- 🧩 Component Routes: Define routes with React components for automatic rendering
- 🪝 React Hooks: useNavigate, useParams, useRoute for seamless integration
- 🎨 Link Component: Type-safe navigation with active state support
- 📦 Simple API: Intuitive and minimal API surface
- ⚡ Fast: Efficient regex-based route matching
- 🔀 Async Navigation: Consistent async behavior across routing modes
# Deno
deno add @itaylor/type-router-react
# npm (use any of npx, yarn dlx, pnpm dlx, or bunx)
npx jsr add @itaylor/type-router-reactnpm install @itaylor/type-router-react
# or
yarn add @itaylor/type-router-react
# or
pnpm add @itaylor/type-router-reactimport React from 'react';
import {
createRouterForReact,
makeComponentRoute,
} from '@itaylor/type-router-react';
// Define components for your routes
function Home() {
return <div>Welcome to the home page!</div>;
}
function About() {
return <div>About us</div>;
}
function UserProfile({ id }: { id: string }) {
return <div>User profile for: {id}</div>;
}
// Create the router with components
const { RouterProvider, Link, ActiveView, useNavigate, useParams } =
createRouterForReact(
[
makeComponentRoute({ path: '/', component: Home }),
makeComponentRoute({ path: '/about', component: About }),
makeComponentRoute({ path: '/user/:id', component: UserProfile }),
] as const,
{},
);
// Your main App component
function App() {
return (
<RouterProvider>
<nav>
<Link to='/'>Home</Link>
<Link to='/about'>About</Link>
<Link to='/user/:id' params={{ id: '123' }}>User 123</Link>
</nav>
<main>
<ActiveView />
</main>
</RouterProvider>
);
}
// Navigation from components
function NavigationExample() {
const navigate = useNavigate();
const params = useParams();
const handleClick = async () => {
await navigate('/user/456');
};
return (
<div>
<p>Current params: {JSON.stringify(params)}</p>
<button onClick={handleClick}>Go to User 456</button>
</div>
);
}You can define routes in two ways:
import {
createRouterForReact,
makeComponentRoute,
makeRoute,
} from '@itaylor/type-router-react';
// Component routes - automatically render components
const componentRoutes = [
makeComponentRoute({
path: '/',
component: Home,
}),
makeComponentRoute({
path: '/user/:id',
component: UserProfile,
}),
] as const;
// Or mix with lifecycle routes for custom behavior
const mixedRoutes = [
makeComponentRoute({ path: '/', component: Home }),
makeRoute({
path: '/dashboard/:section',
onEnter: (params) => {
console.log(`Entering dashboard section: ${params.section}`);
// Custom logic here
},
onExit: (params) => {
console.log(`Leaving dashboard section: ${params.section}`);
},
}),
] as const;
const router = createRouterForReact(mixedRoutes, {});The router provides several React-specific utilities:
function MyComponent() {
// Get current route state
const route = useRoute();
// Get current parameters
const params = useParams();
// Get navigation function
const navigate = useNavigate();
const handleNavigation = async () => {
await navigate('/user/123');
};
return (
<div>
<p>Current path: {route.path}</p>
<p>Params: {JSON.stringify(params)}</p>
<button onClick={handleNavigation}>Navigate</button>
</div>
);
}The Link component provides type-safe navigation with active state support:
function Navigation() {
return (
<nav>
{/* Simple links */}
<Link to='/'>Home</Link>
<Link to='/about'>About</Link>
{/* Links with parameters */}
<Link to='/user/:id' params={{ id: '123' }}>
User Profile
</Link>
{/* Links with styling */}
<Link
to='/dashboard'
className='nav-link'
activeClassName='nav-link-active'
>
Dashboard
</Link>
{/* Links support all standard anchor props */}
<Link to='/external' target='_blank'>
External Link
</Link>
</nav>
);
}The ActiveView component automatically renders the component for the current route:
function App() {
return (
<RouterProvider>
<div className='app'>
<header>
<Navigation />
</header>
<main>
{/* This renders the component for the current route */}
<ActiveView />
</main>
</div>
</RouterProvider>
);
}Make a routes.ts file that imports @itaylor/type-router-react declares the
routes, calls createRouterForReact and exports Link, ActiveView,
RouterProvider, and any of the other functions you need.
Now when you use them, import them from the routes.ts file, they will always
have the correct types for the routes, without you ever having to manually
declare the types again.
routes.ts:
import {
createRouterForReact,
makeComponentRoute,
} from '@itaylor/type-router-react';
import Home from './components/Home';
import About from './components/About';
import Contact from './components/Contact';
const routes = [
makeComponentRoute({ path: '/', component: Home }),
makeComponentRoute({ path: '/about?search', component: About }),
makeComponentRoute({ path: '/contact', component: Contact }),
] as const;
const router = createRouterForReact(routes, { fallbackPath: '/' });
export const {
Link,
ActiveView,
RouterProvider,
useNavigate,
useRoute,
} = router;App.tsx
import { Link, ActiveView } from './routes';
export default function App() {
return (
<div>
<header>
<nav>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
<Link to="/contact">Contact</Link>
</nav>
</header>
<main>
<ActiveView />
</main>
</div>
);type-router-react inherits powerful query parameter support from the base type-router library. Query parameters are declared directly in route paths and provide type-safe, optional parameters that don't affect route matching.
Query parameters are declared using the syntax ?param1¶m2¶m3 after the
path:
import {
createRouterForReact,
makeComponentRoute,
} from '@itaylor/type-router-react';
// Component that handles search with optional query parameters
function SearchPage({ q, category, sort }: {
q?: string;
category?: string;
sort?: string;
}) {
return (
<div>
<h1>Search Results</h1>
<p>Query: {q || 'No query'}</p>
<p>Category: {category || 'All'}</p>
<p>Sort: {sort || 'Default'}</p>
</div>
);
}
// Component for product pages with mixed path and query parameters
function ProductPage({ id, color, size, variant }: {
id: string; // Required path parameter
color?: string; // Optional query parameter
size?: string; // Optional query parameter
variant?: string; // Optional query parameter
}) {
return (
<div>
<h1>Product: {id}</h1>
<p>Color: {color || 'Default'}</p>
<p>Size: {size || 'Standard'}</p>
<p>Variant: {variant || 'Basic'}</p>
</div>
);
}
const { RouterProvider, Link, ActiveView, useNavigate } = createRouterForReact(
[
makeComponentRoute({ path: '/', component: Home }),
makeComponentRoute({
path: '/search?q&category&sort',
component: SearchPage,
}),
makeComponentRoute({
path: '/product/:id?color&size&variant',
component: ProductPage,
}),
] as const,
{},
);The enhanced path-only navigation supports three equivalent patterns:
function NavigationExample() {
const navigate = useNavigate();
const handleSearch = async () => {
// 1. Path template (enhanced) - most readable
await navigate('/search?q&category&sort', {
q: 'typescript',
category: 'programming',
sort: 'newest',
});
// 2. Concrete path (enhanced) - works with any matching route
await navigate('/search', {
q: 'react',
category: 'frontend',
});
// 3. Traditional (still works)
await navigate('/search?q&category&sort', {
q: 'vue',
category: 'frontend',
sort: 'popular',
});
};
const handleProduct = async () => {
// Mixed path and query parameters
await navigate('/product/:id', {
id: 'laptop123', // Required path parameter
color: 'silver', // Optional query parameter
size: '15inch', // Optional query parameter
variant: 'premium', // Optional query parameter
});
};
return (
<div>
<button onClick={handleSearch}>Search</button>
<button onClick={handleProduct}>View Product</button>
</div>
);
}The Link component fully supports query parameters with type safety:
function NavigationLinks() {
return (
<nav>
{/* Links with query parameters */}
<Link
to='/search?q&category&sort'
params={{ q: 'react', category: 'tutorials' }}
>
React Tutorials
</Link>
{/* Mixed path and query parameters */}
<Link
to='/product/:id?color&size'
params={{
id: 'phone456',
color: 'blue',
size: '6.1inch',
}}
>
Blue Phone
</Link>
{/* Links work without query parameters too */}
<Link to='/search'>Search (no filters)</Link>
</nav>
);
}Access query parameters through the component props or hooks:
function ProductDetails() {
const params = useParams(); // Gets all parameters (path + query)
// TypeScript knows the exact types based on the current route
return (
<div>
<h1>Product: {params.id}</h1>
{params.color && <p>Color: {params.color}</p>}
{params.size && <p>Size: {params.size}</p>}
{params.variant && <p>Variant: {params.variant}</p>}
</div>
);
}
// Alternative: Direct prop injection for component routes
function SearchResults({ q, category, sort }: {
q?: string;
category?: string;
sort?: string;
}) {
// Props are automatically injected from route parameters
const hasFilters = category || sort;
return (
<div>
<h1>Search: {q || 'All'}</h1>
{hasFilters && (
<div className='filters'>
{category && <span>Category: {category}</span>}
{sort && <span>Sort: {sort}</span>}
</div>
)}
</div>
);
}- Path parameters are required (
string) - extracted from URL path segments - Query parameters are optional (
string | undefined) - declared after? - Path parameters trump query parameters with the same name
- URL encoding/decoding is handled automatically
- Only declared parameters are extracted; undeclared params are ignored
- Query parameters don't affect route matching - only the path part matters
- Full TypeScript support - parameter types are automatically inferred
// Example showing parameter precedence
const router = createRouterForReact(
[
makeComponentRoute({
path: '/user/:id?id&settings',
component: UserPage,
}),
] as const,
{},
);
// When navigating to: /user/alice?id=ignored&settings=dark
// params.id === 'alice' (from path), not 'ignored' (from query)
// params.settings === 'dark' (from query)Creates a new React router instance with components and hooks.
routes: An array of route definitions (useas constfor best type inference)options: Optional configuration object (same as type-router options)
An object containing:
RouterProvider: React context provider componentLink: Type-safe link componentActiveView: Component that renders the current route's componentuseNavigate: Hook that returns the navigation functionuseParams: Hook that returns current route parameters
Same options as the base type-router library:
| Option | Type | Default | Description |
|---|---|---|---|
urlType |
'hash' | 'history' |
'history' |
Routing mode to use |
fallbackPath |
string |
undefined |
Route to use when no match is found |
autoInit |
boolean |
true |
Automatically initialize routing on creation |
onEnter |
function |
undefined |
Global hook called when entering any route |
onExit |
function |
undefined |
Global hook called when exiting any route |
onParamChange |
function |
undefined |
Global hook called when params change |
onMiss |
function |
undefined |
Called when no route matches |
Helper function that creates a component route with proper TypeScript inference.
const userRoute = makeComponentRoute({
path: '/user/:id',
component: ({ id }: { id: string }) => <div>User: {id}</div>,
});Context provider that must wrap your app to provide routing context.
<RouterProvider>
<App />
</RouterProvider>;Type-safe navigation component.
type LinkProps = {
to: Path;
params?: ParamsFor<Path>;
children?: React.ReactNode;
className?: string;
activeClassName?: string;
// ... all other anchor element props
};Renders the component for the currently active route.
<ActiveView />;Returns the navigation function.
const navigate = useNavigate();
await navigate('/user/123');Returns the current route parameters.
const params = useParams(); // { id: string } for route /user/:idReturns the current route state.
const route = useRoute();
console.log(route.path, route.params, route.route);// Redux/Zustand integration
function App() {
const dispatch = useDispatch();
const router = useMemo(() =>
createRouterForReact(routes, {
onEnter: (route, params) => {
dispatch(routeChanged({ route: route.path, params }));
},
}), [dispatch]);
return (
<router.RouterProvider>
<router.ActiveView />
</router.RouterProvider>
);
}type-router-react provides exceptional TypeScript support:
const { Link, useNavigate } = createRouterForReact([
makeComponentRoute({ path: "/user/:id", component: UserProfile }),
makeComponentRoute({ path: "/post/:category/:slug", component: Post }),
] as const, {});
// ✅ All valid
<Link to="/user/:id" params={{ id: "123" }} />
<Link to="/post/:category/:slug" params={{ category: "tech", slug: "intro" }} />
// ❌ TypeScript errors
<Link to="/unknown" /> {/* Unknown route */}
<Link to="/user/:id" params={{ wrong: "123" }} /> {/* Wrong param */}// TypeScript automatically infers the props from the route parameters
const UserProfile = ({ id }: { id: string }) => {
return <div>User: {id}</div>;
};
makeComponentRoute({
path: '/user/:id',
component: UserProfile, // TypeScript ensures props match params!
});function UserComponent() {
// TypeScript knows the exact type of params based on current route
const params = useParams(); // Typed based on active route
const navigate = useNavigate(); // Typed navigation function
// Type-safe navigation
await navigate('/user/:id', { id: params.id });
}Check out the /examples and test-fixtures directories for complete working
examples including:
- Basic routing setup
- Advanced patterns
MIT