A guard middleware for react-router v6

Last update: Aug 2, 2022

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).

bar
} path="/bar/*"> baz
} path="/bar/baz" /> ) }">
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).

bar
} path="/bar/*"> baz
} path="/bar/baz" /> ) }">
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.

bar
} path="/bar/*" > baz
} path="/bar/baz" /> ) }">
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 {
  Location,
  NavigateFunction,
  RouteMatch,
  RouteObject,
} from 'react-router'
import { ReplacePick } from 'types-kit'

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

export interface GuardedRouteObject extends 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:

loading2...
}> bar
} path="/bar/*" > baz} path="/bar/baz" /> ">
<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
loading...
} guards={[ (to, from, next) => { next() }, ]} /> ">
<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>
  )
}

GitHub

https://github.com/Col0ring/react-router-guarded-routes
You might also like...

Declarative router component for React.

React Router Component Version Compatibility = 0.39.0 React v15,16 = 0.32.0 React v15 = 0.27.0 React 0.14 0.24 - 0.26.0 React 0.13 0.23 - 0.26.0 Re

Jul 19, 2022

React Router scroll management

react-router-scroll React Router scroll management. react-router-scroll is a React Router middleware that adds scroll management using scroll-behavior

Jun 23, 2022

🔼 UI-Router for React

🔼 UI-Router for React

UI-Router provides extremely flexible, state based routing to the React ecosystem.

Aug 5, 2022

Easy Router for Effector with React bindings

effector-easy-router A declarative router for effector and react. It is inspired by react-router-dom and effector gates. Routes are independent from e

Mar 22, 2022

React router that supports rendering of multiple independent (auxiliary) routes.

aux-router React router that supports rendering of multiple independent (auxiliary) routes. Install npm install --save aux-router Documentation AuxRou

Oct 4, 2021

A small router library for React focusing on the Developer Experience

Crossroad A routing library for React with a familiar interface. It has some differences with React Router so you write cleaner code: The links are pl

May 14, 2022

A simple and safe router for React and TypeScript.

A simple and safe router for React and TypeScript.

Jul 25, 2022

Code examples for our React Router 6 update video

About This Repository This repository belongs to my "React Router v6 Upgrade Guide" YouTube video. You can also learn all about React (incl. React Rou

Jul 22, 2022

Full page transitions with react-router.

Full page transitions with react-router.

react-tiger-transition Page transitions for react router dom. Animate your routes programmatically during navigation. Instead of defining the animatio

Aug 2, 2022
A small router middleware for vanilla-js.

A small router middleware for vanilla-js

May 23, 2022
:tada: Redux First History - Redux history binding support react-router - @reach/router - wouter

redux-first-history Redux First History - Make Redux 100% SINGLE-AND-ONLY source of truth! Redux history binding for react-router @reach/router wouter

Jul 30, 2022
Redux bindings for React Router – keep your router state inside your Redux store

redux-router This project is experimental. For bindings for React Router 1.x see here In most cases, you don’t need any library to bridge Redux and Re

Aug 4, 2022
Render isomorphic React + React Router apps and components in Node

Render isomorphic React + React Router apps and components in Node

Feb 27, 2022
named routes for react-router and your react application
named routes for react-router and your react application

react-router-namesake Example import { Switch } from "react-router"; import { BrowserRouter, Route, Link } from "react-router-dom"; import { Router as

Aug 12, 2021
Frontend of agro rent app built with React, Axios, React-router-dom v6 & Bootstrap

Getting Started with Create React App This project was bootstrapped with Create React App. Available Scripts In the project directory, you can run: ya

Dec 8, 2021
React app with TypeScript - React Router Dom

React Project with - React Router Dom My name is Alex Principe. I'm a Full stack developer who shares programming code with the community. This repo c

Dec 12, 2021
You can found the concept of react hooks, local storage, conditional rendering and react-router-dom.

Getting Started with Create React App This project was bootstrapped with Create React App. Available Scripts In the project directory, you can run: np

Jun 1, 2022
a more intuitive way of defining private, public and common routes for react applications using react-router-dom v6

auth-react-router is a wrapper over react-router-dom v6 that provides a simple API for configuring public, private and common routes (React suspense r

Jun 29, 2022
Automatic breadcrumbs for React-Router

React Breadcrumbs React component use to generate a breadcrumb trail (compatible with React Router). Installation npm install --save react-breadcrumbs

Jun 26, 2022