npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@switcher/preact

v2.3.0

Published

Type-safe TypeScript-first minimalistic router for React & Preact apps

Downloads

1,411

Readme

Switcher

Type-safe TypeScript-first minimalistic router for React & Preact apps.

Why?

  • Designed with TypeScript's type inference in mind
  • Universal code (browser & Node.js)
  • Functional API
  • Complete type-safety
  • Autocomplete for routes, params and meta information
  • Say goodbye to any!
  • Say goodbye to exceptions!

Please note that the library depends on Proxy, so it works only in the browsers that support it (meaning no IE support)

Installation

The library is available as npm packages for React and Preact:

# For React
npm install @switcher/react --save
# Or using Yarn:
yarn add @switcher/react

# For Preact
npm install @switcher/preact --save
# Or using Yarn:
yarn add @switcher/preact

Get started

To start using Switcher you need to add a module to your app, i.e. router.ts. There you going to initialize the router and export it's interface to your application.

Here's the router from Fire Beast blog source code:

// When using with React:
import { createRouter, InferRouteRef, route } from '@switcher/react'
// Or Preact:
import { createRouter, InferRouteRef, route } from '@switcher/preact'

// Routes

export const appRoutes = [
  route('home', '/'),

  route('tutorial', (params: { slug: string }) => `/tutorials/${params.slug}`),

  route(
    'tutorial-chapter',
    (params: { tutorialSlug: string; chapterSlug: string }) =>
      `/tutorials/${params.tutorialSlug}/chapters/${params.chapterSlug}`
  ),

  route(
    'post',
    (params: { categoryId: string; postId: string }) =>
      `/${params.categoryId}/${params.postId}`
  )
]

// Routing methods
export const {
  buildHref,
  useRouter,
  RouterContext,
  RouterLink,
  resolveLocation,
  refToLocation
} = createRouter(appRoutes)

// Type to use in prop definitions
export type AppRouteRef = InferRouteRef<typeof appRoutes>

Then you need to initialize the router context in your root component and pass the initial URL:

// When using with React:
import React from 'react'
// Or Preact:
import { h } from 'preact'

import { RouterContext, useRouter } from './router'

export default function UI() {
  const router = useRouter(location.href)

  return (
    <RouterContext.Provider value={router}>
      <Content />
    </RouterContext.Provider>
  )
}

Then you'll be able to access the router context in nested components and render the corresponding page:

// When using with React:
import React, { useContext } from 'preact'
// Or Preact:
import { h } from 'preact'
import { useContext } from 'preact/hooks'

import { RouterContext } from './router'
import HomePage from './HomePage'
import PostPage from './PostPage'
import TutorialPage from './TutorialPage'
import TutorialChapterPage from './TutorialChapterPage'
import NotFoundPage from './NotFoundPage'

export default function Content() {
  const { location } = useContext(RouterContext)

  switch (location.name) {
    case 'home':
      return <HomePage />

    case 'post': {
      const { postId } = location.params
      return <PostPage postId={postId} />
    }

    case 'tutorial': {
      const { slug } = location.params
      return <TutorialPage slug={slug} />
    }

    case 'tutorial-chapter': {
      const { tutorialSlug, chapterSlug } = location.params
      return (
        <TutorialChapterPage
          tutorialSlug={tutorialSlug}
          chapterSlug={chapterSlug}
        />
      )
    }

    case '404':
    default:
      return <NotFoundPage />
  }
}

To navigate between the pages you could use RouterLink or navigate function from the router context:

// When using with React:
import React, { useContext } from 'react'
// Or Preact:
import { h } from 'preact'
import { useContext } from 'preact/hooks'

import { RouterContext, RouterLink } from '#app/router'
import { signOut } from './auth'

export default function Navigation() {
  const { navigate } = useContext(RouterContext)

  return (
    <ul>
      <li>
        <RouterLink to={{ name: 'home' }}>Home</RouterLink>
      </li>

      <li>
        <button onClick={() => signOut().then(() => navigate({ to: 'home' }))}>
          Sign out
        </button>
      </li>
    </ul>
  )
}

API

route

The function creates a route that later can be passed to createRouter.

To define a simple route:

// When using with React:
import { route } from '@switcher/react'
// Or Preact:
import { route } from '@switcher/preact'

route('home', '/')

To define a route with params:

route(
  'tutorial-chapter',
  (params: { tutorialSlug: string; chapterSlug: string }) =>
    `/tutorials/${params.tutorialSlug}/chapters/${params.chapterSlug}`
)

The route also allows to pass meta information:

const routes = [
  route('login', '/login', { auth: false }),
  route('projects', '/projects', { auth: true })
]

createRouter

The function accepts an array of routes created using route and router options and returns the router API with binded types.

// When using with React:
import { route, createRouter } from '@switcher/react'
// Or Preact:
import { route, createRouter } from '@switcher/preact'

const routes = [
  route('login', '/login', { auth: false }),
  route('projects', '/projects', { auth: true })
]

const routerAPI = createRouter(routes, {
  // Configure "missing hash" behavior. When user clicks
  // a hash link (#whatever), the router will try to find
  // an element with id "whatever". When such element
  // is missing:
  // - 'top' (default): scroll to the top
  // - 'preserve': keep the current scroll position
  scrollOnMissingHash: 'preserve'
})

The router API consist of these methods:

See docs below for more information about each of those methods.

useRouter

React/Preact hook that initializes routing with the current URL and returns component-level router API.

// When using with React:
import React from 'react'
// Or Preact:
import { h } from 'preact'

import { RouterContext, useRouter } from './router'

export default function UI() {
  // Initializes router with the initial URL
  const componentRouterAPI = useRouter(location.href)

  return (
    <RouterContext.Provider value={componentRouterAPI}>
      <Content />
    </RouterContext.Provider>
  )
}

Right after initializing the router, pass the value to RouterContext, to make it available in nested components.

The component-level router API consist of these methods:

See docs below for more information about each of those methods.

location (component-level API)

An object with the information about the current location:

// Route location for the following URL: https://firebeast.dev/tutorials/firebase-react-quick-start/chapters/firestore-queries?ref=twitter#recap
{
  // The route name
  name: 'tutorial-chapter',
  // The location params
  params: {
    tutorialSlug: 'firebase-react-quick-start',
    chapterSlug: 'firestore-queries'
  },
  // The parsed query
  query: { ref: 'twitter' },
  // The hash
  hash: 'recap',
  // The route meta information (copied from the route definition; in this case empty)
  meta: {},
  // The landing information; it tells how the app gets landed at the location
  landing: { redirected: true }
}

If the current URL doesn't match to any routes:

{
  name: '404',
  params: undefined,
  query: {},
  hash: '',
  meta: undefined
}
navigate (component-level API)

The function performs navigation to the given route reference:

// Navigate to the home route
navigate({ name: 'home' })

// Navigate to route with params, query and hash
navigate({
  name: 'tutorial-chapter',
  params: {
    tutorialSlug: 'firebase-react-quick-start',
    chapterSlug: 'firestore-queries'
  },
  query: { ref: 'twitter' },
  hash: 'recap'
})
redirect (component-level API)

The function performs navigation to the given route reference and sets redirected to the landing property of the location.

// Redirect to the home route
redirect({ name: 'home' })

// Redirect to route with params, query and hash
redirect({
  name: 'tutorial-chapter',
  params: {
    tutorialSlug: 'firebase-react-quick-start',
    chapterSlug: 'firestore-queries'
  },
  query: { ref: 'twitter' },
  hash: 'recap'
})
buildHref (component-level API)

The function builds href to the given route reference:

buildHref({ name: 'home' })
//=> "/"

buildHref({
  name: 'tutorial-chapter',
  params: {
    tutorialSlug: 'firebase-react-quick-start',
    chapterSlug: 'firestore-queries'
  },
  query: { ref: 'twitter' },
  hash: 'recap'
})
//=> "/tutorials/firebase-react-quick-start/chapters/firestore-queries?ref=twitter#recap"

RouterContext

React/Preact context that propagates the component-level API.

First, initialize the router using useRouter and pass the component-level API to RouterContext.Provider:

// When using with React:
import React from 'react'
// Or Preact:
import { h } from 'preact'

import { RouterContext, useRouter } from './router'

export default function UI() {
  const componentRouterAPI = useRouter(location.href)

  return (
    <RouterContext.Provider value={componentRouterAPI}>
      <Content />
    </RouterContext.Provider>
  )
}

Then use useContext to access it:

// When using with React:
import React, { useContext } from 'react'
// Or Preact:
import { h } from 'preact'
import { useContext } from 'preact/hooks'

import { RouterContext } from '#app/router'
import { signOut } from './auth'

export default function SignOutButton() {
  const { navigate } = useContext(RouterContext)

  return (
    <button onClick={() => signOut().then(() => navigate({ to: 'home' }))}>
      Sign out
    </button>
  )
}

RouterLink

React/Preact component that renders a element with binded href, onClick, etc. props:

// When using with React:
import React from 'react'
// Or Preact:
import { h } from 'preact'

import { RouterLink } from './router'
import { Post } from './types'

export default function PostsList({ posts }: { posts: Post[] }) {
  return (
    <ul>
      {posts.map(post => (
        <li key={post.ref.id}>
          <RouterLink to={{ name: 'post', params: { postId: post.ref.id } }}>
            {post.data.title}
          </RouterLink>
        </li>
      ))}
    </ul>
  )
}

You can customize the rendered component using component prop:

// When using with React:
import React, { ReactNode } from 'react'
// On with Preact:
import { h, ComponentChildren } from 'preact'

import { RouterLink, AppRouteRef } from './router'
import { ButtonProps, Button } from './Button'

type Props = {
  to: AppRouteRef
  // When using with React:
  children?: ReactNode[]
  // On with Preact:
  children?: ComponentChildren
} & ButtonProps

export default function ButtonLink({ to, children, ...props }: Props) {
  return (
    <RouterLink to={to} component={<Button tag="a" {...props} />}>
      {children}
    </RouterLink>
  )
}

The component also can render a block (div by default) instead of an anchor. Set mode prop to block:

// When using with React:
import React from 'react'
// Or Preact:
import { h } from 'preact'

import { RouterLink } from './router'
import { Post } from './types'

export default function PostsList({ posts }: { posts: Post[] }) {
  return (
    <ul>
      {posts.map(post => (
        <RouterLink
          mode="block"
          tag="li"
          to={{ name: 'post', params: { postId: post.ref.id } }}
          key={post.ref.id}
        >
          <RouterLink to={{ name: 'post', params: { postId: post.ref.id } }}>
            {post.data.title}
          </RouterLink>

          <ul>
            {post.data.tags.map(tag => (
              <li>
                <RouterLink to={{ name: 'tag', params: { tag } }}>
                  {tag}
                </RouterLink>
              </li>
            ))}
          </ul>
        </RouterLink>
      ))}
    </ul>
  )
}

resolveLocation

The function accepts URL string and returns a location object:

resolveLocation(
  'https://firebeast.dev/tutorials/firebase-react-quick-start/chapters/firestore-queries?ref=twitter#recap'
)
//=> {
//=>   name: 'tutorial-chapter',
//=>   params: {
//=>     tutorialSlug: 'firebase-react-quick-start',
//=>     chapterSlug: 'firestore-queries'
//=>   },
//=>   query: { ref: 'twitter' },
//=>   hash: 'recap',
//=>   meta: undefined
//=> }

refToLocation

The function accepts a reference object and returns corresponding location object:

refToLocation({
  name: 'tutorial-chapter',
  params: {
    tutorialSlug: 'firebase-react-quick-start',
    chapterSlug: 'firestore-queries'
  },
  query: { ref: 'twitter' },
  hash: 'recap'
})
//=> {
//=>   name: 'tutorial-chapter',
//=>   params: {
//=>     tutorialSlug: 'firebase-react-quick-start',
//=>     chapterSlug: 'firestore-queries'
//=>   },
//=>   query: { ref: 'twitter' },
//=>   hash: 'recap',
//=>   meta: undefined
//=> }

buildHref

The function builds href to the given route reference:

buildHref({ name: 'home' })
//=> "/"

buildHref({
  name: 'tutorial-chapter',
  params: {
    tutorialSlug: 'firebase-react-quick-start',
    chapterSlug: 'firestore-queries'
  },
  query: { ref: 'twitter' },
  hash: 'recap'
})
//=> "/tutorials/firebase-react-quick-start/chapters/firestore-queries?ref=twitter#recap"

Changelog

See the changelog.

License

MIT © Sasha Koss