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

react-router-guarded-routes

v0.4.5

Published

a guard middleware for react-router v6

Downloads

5,702

Readme

React-Router-Guarded-Routes

English | 简体中文

A guard middleware for react-router v6, inspired by react-router-guards.

Install

npm install react-router-guarded-routes react-router --save
# or
yarn add react-router-guarded-routes react-router
# or
pnpm add react-router-guarded-routes react-router

Usage

Basic

Provides GuardConfigProvider in BrowserRouter, and you can use it like react-router (compatible with the apis of react-router).

import { BrowserRouter } from 'react-router-dom'
import {
  GuardConfigProvider,
  GuardedRoute,
  GuardedRoutes,
} from 'react-router-guarded-routes'

export default function App() {
  return (
    <BrowserRouter>
      <GuardConfigProvider>
        <GuardedRoutes>
          <GuardedRoute element={<div>foo</div>} path="/foo" />
          <GuardedRoute element={<div>bar</div>} path="/bar/*">
            <GuardedRoute element={<div>baz</div>} path="/bar/baz" />
          </GuardedRoute>
        </GuardedRoutes>
      </GuardConfigProvider>
    </BrowserRouter>
  )
}

Use hooks:

import {
  GuardedRouteObject,
  useGuardedRoutes,
} from 'react-router-guarded-routes'

const routes: GuardedRouteObject[] = [
  { path: '/foo', element: <div>foo</div> },
  {
    path: '/bar/*',
    element: <div>bar</div>,
    children: [{ path: '/bar/baz', element: <div>baz</div> }],
  },
]

function Routes() {
  return <GuardedRoutes>{useGuardedRoutes([routes])}</GuardedRoutes>
}

export default function App() {
  return (
    <BrowserRouter>
      <GuardConfigProvider>
        <Routes />
      </GuardConfigProvider>
    </BrowserRouter>
  )
}

Guarding

You can provide GuardProvider with multiple guards middleware for route guarding, GuardProvider can receive an array of guards and a fallback element (can be used to load loading state).

import { BrowserRouter } from 'react-router-dom'
import {
  GuardConfigProvider,
  GuardedRoute,
  GuardedRoutes,
  GuardMiddleware,
  GuardProvider,
} from 'react-router-guarded-routes'

const logGuard: GuardMiddleware = (to, from, next) => {
  console.log(to) // { location, matches, route }
  console.log(from)
  next() // call next function to run the next middleware or show the route element, it accepts the same parameters as navigate (useNavigate()) and behaves consistently.
}

// you can use object to determine whether you need to register middleware
const barGuard: GuardMiddleware = {
  handler: (to, from, next) => {
    console.log('bar')
    next()
  },
  register: (to, from) => {
    // only matched with `/bar` can be executed.
    if (to.location.pathname.startsWith('/bar')) {
      return true
    }
    return false
  },
}

const guards = [logGuard, barGuard]

export default function App() {
  return (
    <BrowserRouter>
      <GuardConfigProvider>
        {/* Guard all routes below. */}
        <GuardProvider fallback={<div>loading...</div>} guards={guards}>
          <GuardedRoutes>
            <GuardedRoute element={<div>foo</div>} path="/foo" />
            <GuardedRoute element={<div>bar</div>} path="/bar/*">
              <GuardedRoute element={<div>baz</div>} path="/bar/baz" />
            </GuardedRoute>
          </GuardedRoutes>
        </GuardProvider>
      </GuardConfigProvider>
    </BrowserRouter>
  )
}

Of course, you can also set up separate fallbacks and guards for each route.

import { BrowserRouter, Outlet } from 'react-router-dom'
import {
  GuardConfigProvider,
  GuardedRoute,
  GuardedRoutes,
  GuardMiddleware,
  GuardProvider,
} from 'react-router-guarded-routes'

const logGuard: GuardMiddleware = (to, from, next) => {
  console.log(to, from)
  next()
}

const fooGuard: GuardMiddleware = (to, from, next) => {
  console.log('foo')
  next()
}

const guards = [logGuard]
const fooGuards = [fooGuard]

export default function App() {
  return (
    <BrowserRouter>
      <GuardConfigProvider>
        <GuardProvider fallback={<div>loading...</div>} guards={guards}>
          <GuardedRoutes>
            <GuardedRoute
              fallback={<div>loading foo...</div>}
              guards={fooGuard}
              element={<div>foo</div>}
              path="/foo"
            />
            <GuardedRoute
              element={
                <div>
                  bar
                  <Outlet />
                </div>
              }
              path="/bar/*"
            >
              <GuardedRoute element={<div>baz</div>} path="/bar/baz" />
            </GuardedRoute>
          </GuardedRoutes>
        </GuardProvider>
      </GuardConfigProvider>
    </BrowserRouter>
  )
}

You can also call next.ctx('ctx value') to transfer contextual information, and get it by ctxValue in the next guard middleware. The guard middleware is executed from outside to inside, left to right.

<GuardConfigProvider>
  <GuardProvider
    fallback={<div>loading...</div>}
    guards={(to, from, next) => {
      next.ctx('ctx value')
    }}
  >
    <GuardedRoutes>
      <GuardedRoute
        guards={(to, from, next, { ctxValue }) => {
          console.log(ctxValue) // ctx value
          next()
        }}
        element={<div>foo</div>}
        path="/foo"
      />
    </GuardedRoutes>
  </GuardProvider>
</GuardConfigProvider>

And call next.end() to ignore remaining middleware.

<GuardConfigProvider>
  <GuardProvider
    fallback={<div>loading...</div>}
    guards={
      ((to, from, next) => {
        next.end()
      },
      () => {
        console.log('will not be called')
      })
    }
  >
    <GuardedRoutes>
      <GuardedRoute
        guards={() => {
          console.log('will not be called')
        }}
        element={<div>foo</div>}
        path="/foo"
      />
    </GuardedRoutes>
  </GuardProvider>
</GuardConfigProvider>

API

Types

import React from 'react'
import {
  Location,
  NavigateFunction,
  RouteMatch,
  RouteObject,
} from 'react-router'
import { ReplacePick } from 'types-kit'

export interface GuardedRouteConfig {
  guards?: GuardMiddleware[]
  fallback?: React.ReactNode
  [props: PropertyKey]: any
}

export type GuardedRouteObject = RouteObject &
  GuardedRouteConfig & {
    children?: GuardedRouteObject[]
  }

export interface NextFunction<T> extends NavigateFunction {
  (): void
  ctx: (value: T) => void
  end: () => void
}

export interface GuardedRouteMatch<ParamKey extends string = string>
  extends Omit<RouteMatch<ParamKey>, 'route'> {
  route: GuardedRouteObject
}

export interface ToGuardRouteOptions {
  location: Location
  matches: GuardedRouteMatch[]
  route: GuardedRouteObject
}

export interface FromGuardRouteOptions
  extends ReplacePick<
    ToGuardRouteOptions,
    ['location', 'route'],
    [
      ToGuardRouteOptions['location'] | null,
      ToGuardRouteOptions['route'] | null
    ]
  > {}

export interface ExternalOptions<T, I> {
  ctxValue: T
  injectedValue: I
}

export type GuardMiddlewareFunction<T = any, I = any> = (
  to: ToGuardRouteOptions,
  from: FromGuardRouteOptions,
  next: NextFunction<T>,
  externalOptions: ExternalOptions<T, I>
) => Promise<void> | void

export type GuardMiddlewareObject<T = any, I = any> = {
  handler: GuardMiddlewareFunction<T, I>
  register?: (
    to: ToGuardRouteOptions,
    from: FromGuardRouteOptions
  ) => Promise<boolean> | boolean
}
export type GuardMiddleware<T = any, I = any> =
  | GuardMiddlewareFunction<T, I>
  | GuardMiddlewareObject<T, I>

Components

GuardConfigProvider

The GuardConfigProvider has configuration about routing, should not be used more than one in an app, make sure it's at the topmost level inside the Router (BrowserRouter and HashRouter).

And it provides APIs for whether to run guard middleware and whether to display the fallback element:

Props
import React from 'react'

export interface GuardConfigProviderProps {
  enableGuard?: (
    location: ToGuardRouteOptions,
    prevLocation: FromGuardRouteOptions
  ) => Promise<boolean> | boolean
  enableFallback?: (
    location: ToGuardRouteOptions,
    prevLocation: FromGuardRouteOptions
  ) => boolean
  children: React.ReactNode
}

| Prop | Optional | Default | Description | | ---------------- | :------: | :------------------------------------------------------------: | --------------------------------------- | | enableGuards | Yes | (to, from) => to.location.pathname !== from.location?.pathname | whether to run guard middleware | | enableFallback | Yes | () => true | whether to display the fallback element |

Setup
import { BrowserRouter } from 'react-router-dom'
import { GuardConfigProvider } from 'react-router-guarded-routes'
export default function App() {
  return (
    <BrowserRouter>
      <GuardConfigProvider>
        {
          // routes
        }
      </GuardConfigProvider>
    </BrowserRouter>
  )
}

GuardProvider

It provides public fallback element and guard middleware for GuardedRoute.

Props
import React from 'react'

export interface GuardProviderProps {
  fallback?: React.ReactElement
  useInject?: (
    to: ToGuardRouteOptions,
    from: FromGuardRouteOptions
  ) => Record<string, any>
  guards?: GuardedRouteConfig['guards']
  children: React.ReactNode
}

| Prop | Optional | Default | Description | | ----------- | :------: | :-----: | ------------------------------------------------------------------------------------------------------------------------------------------ | | fallback | Yes | | a fallback element to show when a GuardedRoute run guard middleware | | useInject | Yes | | an injected value (React hooks can be used) for guard middleware to use, will be automatically merged the values of nested GuardProvider | | guards | Yes | | the guards to set for routes inside the GuardProvider |

Setup
import { BrowserRouter } from 'react-router-dom'
import {
  GuardConfigProvider,
  GuardedRoute,
  GuardedRoutes,
  GuardMiddleware,
  GuardProvider,
} from 'react-router-guarded-routes'

const logGuard: GuardMiddleware = (to, from, next) => {
  console.log(to, from)
  next()
}

export default function App() {
  return (
    <BrowserRouter>
      <GuardConfigProvider>
        <GuardProvider fallback={<div>loading...</div>} guards={[logGuard]}>
          <GuardedRoutes>
            <GuardedRoute element={<div>foo</div>} path="/foo" />
          </GuardedRoutes>
        </GuardProvider>
      </GuardConfigProvider>
    </BrowserRouter>
  )
}

Use nested GuardProvider:

<GuardConfigProvider>
  <GuardProvider fallback={<div>loading...</div>}>
    <GuardedRoutes>
      <GuardedRoute element={<div>foo</div>} path="/foo" />
      <GuardProvider fallback={<div>loading2...</div>}>
        <GuardedRoute
          element={
            <div>
              bar
              <Outlet />
            </div>
          }
          path="/bar/*"
        >
          <GuardedRoute element={<div>baz</div>} path="/bar/baz" />
        </GuardedRoute>
      </GuardProvider>
    </GuardedRoutes>
  </GuardProvider>
</GuardConfigProvider>

Inject value:

import { createContext } from 'react'
import { BrowserRouter } from 'react-router-dom'
import {
  GuardConfigProvider,
  GuardedRoute,
  GuardedRoutes,
  GuardProvider,
} from 'react-router-guarded-routes'

export const AuthContext = createContext({
  isLogin: false,
})

export function useAuth() {
  return useContext(AuthContext)
}

export default function App() {
  return (
    <BrowserRouter>
      <AuthContext>
        <GuardConfigProvider>
          <GuardProvider
            fallback={<div>loading...</div>}
            useInject={useAuth}
            guards={[
              (to, from, next, { injectedValue }) => {
                console.log(injectedValue) // { isLogin: false }
                next()
              },
            ]}
          >
            <GuardedRoutes>
              <GuardedRoute element={<div>foo</div>} path="/foo" />
            </GuardedRoutes>
          </GuardProvider>
        </GuardConfigProvider>
      </AuthContext>
    </BrowserRouter>
  )
}

GuardedRoutes

The GuardedRoutes component acts as a replacement for the default Routes component provided by React Router.

Props
import { RoutesProps } from 'react-router'

export interface GuardedRoutesProps extends RoutesProps {}
Setup
<BrowserRouter>
  <GuardConfigProvider>
    <GuardedRoutes>
      <GuardedRoute element={<div>foo</div>} path="/foo" />
    </GuardedRoutes>
  </GuardConfigProvider>
</BrowserRouter>

GuardedRoute

The GuardedRoute component acts as a replacement for the default Route component provided by React Router, allowing for routes to use guard middleware and accepting the same props as regular Route.

Props
import { Route } from 'react-router'
type RouteProps = Parameters<typeof Route>[0]

export type GuardedRouteProps = RouteProps & GuardedRouteConfig

The following table explains the guard-specific props for this component.

| Prop | Optional | Default | Description | | ---------- | :------: | :-----: | ---------------------------------------------------------------------------------------------------------------------------------- | | fallback | Yes | | a fallback element to show when a GuardedRoute run guard middleware. (it will override the fallback provided by GuardProvider) | | guards | Yes | | the guards to set for the route |

Setup
<GuardedRoutes>
  <GuardedRoute
    element={<div>foo</div>}
    path="/foo"
    fallback={<div>loading...</div>}
    guards={[
      (to, from, next) => {
        next()
      },
    ]}
  />
</GuardedRoutes>

Hooks

useGuardedRoutes

The useGuardedRoutes hook acts as a replacement for the default useRoutes hook provided by React Router, and additionally provides fallback and guards properties for each member.

Props
import { useRoutes } from 'react-router'

type LocationArg = Parameters<typeof useRoutes>[1]

export function useGuardedRoutes(
  guardedRoutes: GuardedRouteObject[],
  locationArg?: LocationArg
): ReturnType<typeof useRoutes>
Setup
import {
  GuardedRouteObject,
  useGuardedRoutes,
} from 'react-router-guarded-routes'
const routes: GuardedRouteObject[] = [
  {
    path: '/foo',
    element: <div>foo</div>,
    fallback: <div>loading foo...</div>,
    guards: [(to, from, next) => next()],
  },
]

function Routes() {
  return <>{useGuardedRoutes(routes)}</>
}

export default function App() {
  return (
    <BrowserRouter>
      <GuardConfigProvider>
        <GuardProvider fallback={<div>loading...</div>}>
          <Routes>
        </GuardProvider>
      </GuardConfigProvider>
    </BrowserRouter>
  )
}