MyCuppa

@mycuppa/router

A lightweight, type-safe routing library for React applications with support for route parameters, query strings, and browser history.

Installation

npm install @mycuppa/router react react-dom
# or
pnpm add @mycuppa/router react react-dom

Quick Start

import { createRouter, setRouter, useRouter } from '@mycuppa/router'

// Create and configure router
const router = createRouter({
  mode: 'history', // or 'hash'
  routes: [
    { path: '/', handler: () => import('./pages/Home') },
    { path: '/about', handler: () => import('./pages/About') },
    { path: '/user/:id', handler: () => import('./pages/User') },
  ],
})

// Set as global router
setRouter(router)

// Use in components
function App() {
  const { navigate, routeMatch } = useRouter()

  return (
    <div>
      <nav>
        <button onClick={() => navigate('/')}>Home</button>
        <button onClick={() => navigate('/about')}>About</button>
      </nav>

      <p>Current path: {routeMatch?.path}</p>
    </div>
  )
}

Features

Route Parameters

Extract dynamic segments from URLs:

import { useParams } from '@mycuppa/router'

// Route: /user/:id
function UserProfile() {
  const params = useParams()

  return <h1>User ID: {params.id}</h1>
}

// Navigate to: /user/123
// params = { id: '123' }

Query Parameters

Parse and access query strings:

import { useQuery } from '@mycuppa/router'

// URL: /search?q=typescript&tags=web,mobile
function SearchResults() {
  const query = useQuery()

  return (
    <div>
      <p>Search: {query.q}</p>
      {/* query.q = 'typescript' */}

      <p>Tags: {query.tags?.join(', ')}</p>
      {/* query.tags = ['web', 'mobile'] */}
    </div>
  )
}

Navigation

Programmatic navigation with multiple methods:

import { useRouter } from '@mycuppa/router'

function Navigation() {
  const { navigate, back, forward, go } = useRouter()

  return (
    <div>
      {/* Navigate to path */}
      <button onClick={() => navigate('/about')}>About</button>

      {/* Navigate with query params */}
      <button
        onClick={() => navigate('/search', { q: 'typescript' })}
      >
        Search
      </button>

      {/* Browser navigation */}
      <button onClick={back}>Back</button>
      <button onClick={forward}>Forward</button>
      <button onClick={() => go(-2)}>Go back 2 pages</button>
    </div>
  )
}

Route Matching

Get current route information:

import { useRouteMatch } from '@mycuppa/router'

function CurrentRoute() {
  const match = useRouteMatch()

  if (!match) {
    return <p>No route matched</p>
  }

  return (
    <div>
      <p>Path: {match.path}</p>
      <p>Params: {JSON.stringify(match.params)}</p>
      <p>Query: {JSON.stringify(match.query)}</p>
    </div>
  )
}

Configuration Options

import { createRouter } from '@mycuppa/router'

const router = createRouter({
  // Routing mode
  mode: 'history', // or 'hash'

  // Base path for all routes
  basePath: '/app',

  // Route definitions
  routes: [
    { path: '/', handler: async () => import('./pages/Home') },
    { path: '/about', handler: async () => import('./pages/About') },
    { path: '/user/:id', handler: async () => import('./pages/User') },
    {
      path: '/blog/:slug',
      handler: async () => import('./pages/BlogPost'),
    },
  ],
})

Advanced Usage

Route Guards

Protect routes with authentication:

import { useRouter } from '@mycuppa/router'
import { useAuth } from '@mycuppa/auth'

function ProtectedRoute({ children }) {
  const { navigate } = useRouter()
  const { isAuthenticated } = useAuth()

  if (!isAuthenticated) {
    navigate('/login')
    return null
  }

  return <>{children}</>
}

Lazy Loading

Routes support lazy loading with React.lazy:

import { lazy, Suspense } from 'react'

const Home = lazy(() => import('./pages/Home'))
const About = lazy(() => import('./pages/About'))

function App() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <Home />
    </Suspense>
  )
}

Custom Link Component

import { useRouter } from '@mycuppa/router'

function Link({
  to,
  children,
  ...props
}: {
  to: string
  children: React.ReactNode
}) {
  const { navigate } = useRouter()

  const handleClick = (e: React.MouseEvent) => {
    e.preventDefault()
    navigate(to)
  }

  return (
    <a href={to} onClick={handleClick} {...props}>
      {children}
    </a>
  )
}

// Usage
<Link to="/about">About Us</Link>

API Reference

createRouter(config)

Create a new router instance.

Config:

  • mode?: 'history' | 'hash' - Routing mode (default: 'history')
  • basePath?: string - Base path for all routes
  • routes: Route[] - Route definitions

Returns: Router instance

Router Methods

  • navigate(path, query?) - Navigate to a path
  • back() - Go back in history
  • forward() - Go forward in history
  • go(delta) - Go to a specific history offset
  • match(path) - Match a path against routes
  • subscribe(callback) - Subscribe to route changes

React Hooks

  • useRouter() - Get router instance and state
  • useRouteMatch() - Get current route match
  • useParams() - Get route parameters
  • useQuery() - Get query parameters

Route Object

interface Route {
  path: string // Route path pattern
  handler: () => Promise<any> // Lazy import function
}

RouteMatch Object

interface RouteMatch {
  path: string // Matched path
  params: Record<string, string> // Route parameters
  query: Record<string, string | string[]> // Query parameters
}

Best Practices

  1. Use history mode - Prefer history over hash mode for cleaner URLs
  2. Lazy load routes - Use dynamic imports for code splitting
  3. Type-safe params - Validate route parameters at runtime
  4. Query arrays - Use tags=a&tags=b format for array query params
  5. Route guards - Implement authentication checks in components

Examples

See the examples directory for complete routing examples.