@itaylor/type-router-react
v1.0.3
Published
React bindings for type-router - A lightweight, type-safe router with first-class TypeScript support
Maintainers
Readme
type-router-react
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.
Features
- 🎯 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
- 🔌 Custom Path Extractors: Support for any path syntax via pluggable extractors
- 📦 Simple API: Intuitive and minimal API surface
- ⚡ Fast: Efficient regex-based route matching
- 🔀 Async Navigation: Consistent async behavior across routing modes
Installation
From JSR
# Deno
deno add @itaylor/type-router-react
# npm (use any of npx, yarn dlx, pnpm dlx, or bunx)
npx jsr add @itaylor/type-router-reactFrom npm
npm install @itaylor/type-router-react
# or
yarn add @itaylor/type-router-react
# or
pnpm add @itaylor/type-router-reactQuick Start
import 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>
);
}Core Concepts
Component Routes vs Lifecycle Routes
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, {});React Components and Hooks
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>
);
}Link Component
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>
);
}Active View Rendering
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>
);
}Recommended Usage pattern
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>
);Query Parameters
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.
Declaring Query Parameters
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,
{},
);Navigation with Query Parameters
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>
);
}Links with Query Parameters
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>
);
}Using Query Parameters in Components
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>
);
}Key Features
- Path parameters are required (
string) - extracted from URL path segments - Query parameters are optional (
string | undefined) - declared after? - Typed query parameters - Use
?flag=bool,?count=number,?tags=string[]for type-safe boolean, number, and array params - 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)Typed Query Parameters
type-router-react supports typed query parameters for boolean, number, and array types:
// Component with typed query parameters
function SearchResults({
q,
featured,
minPrice,
tags,
}: {
q?: string; // Regular string param
featured?: boolean; // Boolean param
minPrice?: number; // Number param
tags?: string[]; // Array param
}) {
return (
<div>
<h1>Search: {q || 'All'}</h1>
{featured !== undefined && <p>Featured only: {featured ? 'Yes' : 'No'}
</p>}
{minPrice && <p>Min price: ${minPrice}</p>}
{tags && <p>Tags: {tags.join(', ')}</p>}
</div>
);
}
const router = createRouterForReact(
[
makeComponentRoute({
path: '/search?q&featured=bool&minPrice=number&tags=string[]',
component: SearchResults,
}),
] as const,
{},
);
// Navigate with typed params
await navigate('/search?q&featured=bool&minPrice=number&tags=string[]', {
q: 'laptop',
featured: true, // Boolean
minPrice: 500, // Number
tags: ['electronics', 'sale'], // Array
});
// In URL: /search?q=laptop&featured=true&minPrice=500&tags=electronics,sale
// Parsed params: { q: 'laptop', featured: true, minPrice: 500, tags: ['electronics', 'sale'] }Boolean parameters accept:
?featuredor?featured=true→true?featured=false→false- Absence →
undefined
Number parameters are parsed with Number() and validate as actual numbers.
Array parameters are comma-separated strings split into arrays.
API Reference
createRouterForReact(routes, options?)
Creates a new React router instance with components and hooks.
Parameters
routes: An array of route definitions (useas constfor best type inference)options: Optional configuration object (same as type-router options)
Returns
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
Options
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 |
makeComponentRoute(route)
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>,
});React Components
<RouterProvider>
Context provider that must wrap your app to provide routing context.
<RouterProvider>
<App />
</RouterProvider>;<Link>
Type-safe navigation component.
type LinkProps = {
to: Path;
params?: ParamsFor<Path>;
children?: React.ReactNode;
className?: string;
activeClassName?: string;
// ... all other anchor element props
};<ActiveView>
Renders the component for the currently active route.
<ActiveView />;React Hooks
useNavigate()
Returns the navigation function.
const navigate = useNavigate();
await navigate('/user/123');useParams()
Returns the current route parameters.
const params = useParams(); // { id: string } for route /user/:iduseRoute()
Returns the current route state.
const route = useRoute();
console.log(route.path, route.params, route.route);Integration with State Management
// 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>
);
}TypeScript Benefits
type-router-react provides exceptional TypeScript support:
Type-Safe Navigation
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 */}Inferred Component Props
// 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!
});Hook Type Safety
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 });
}Examples
Check out the /examples directory for complete working examples including:
basic.tsx- Basic routing setup with standard URL pathscustom-path-extractor.ts- Custom path syntax implementation (dot-separated paths)custom-path-extractor-react.tsx- React bindings for custom path extractorcustom-path-demo.tsx- Complete demo using custom path syntax
See test-fixtures/ for additional browser-based examples.
Custom Path Extractors
type-router-react supports custom path syntaxes through integration with type-router's pluggable path extractor system. This allows you to use any path syntax you prefer while maintaining full type safety.
Default Syntax (URL-style)
The standard import provides URL-style paths: /users/:id?search
Custom Syntax Example
You can create React bindings for any custom path syntax:
// Dot-separated paths with typed parameters
const routes = [
makeCustomComponentRoute({
path: 'user.{id:number}|active,admin',
component: ({ id, active, admin }) => {
// id: number (type-safe!)
// active: string | undefined
// admin: string | undefined
return <div>User {id}</div>;
},
}),
] as const;
const { RouterProvider, Link } = createCustomRouterForReact(routes, {
urlType: 'hash',
});For complete documentation on creating custom path extractors for React, see CUSTOM_PATH_EXTRACTORS.md.
For examples of custom path extractors, see the /examples directory.
License
MIT
