react-router-dom v6.5.0 implements routing guard RouterBeforeEach and caching

I. Overview

Recently, I noticed that react-router-dom has been updated to version 6.6.1, and a lot of functions have been added in this version. After researching, you can use the API provided by it to implement a Vue-like routing guard, so as to easily achieve the business scenario of routing authentication. Here I use the npm package, which is react-router-dom v6.5.0, but there are basically new functions.

Github:code source

react-router-dom official website

Two, createHashRouter

This function can help us generate a route programmatically.

import { createHashRouter,useRouteError,redirect} from "react-router-dom";
import { Suspense, lazy } from 'react'
import React from "react";
import KeepAlive from 'react-activation'

type IRouterBeforeLoad = (res:any, redirectUrl: string) => Boolean;
let routerLoader: IRouterBeforeLoad;
let _redirectUrl: string = "/";
const routes = [
  {
    path: '/',
    auth: false,
    name:"login",
    component:lazy(() => import('@/page/login/login'))
  },
  {
    path: '/Portal',
    name:"Portal",
    component:lazy(() => import('@/page/portal/portal')),
    children: [
      { 
        path: '/Portal/Home',
        name:"Home",
        component:lazy(() => import('@/page/home/home'))
      },
      { 
        path: '/Portal/Lifecycle',
        name:"Lifecycle",
        component:lazy(() => import('@/page/lifecycle/lifecycle'))
      },
      { 
        path: '/Portal/NotFound',
        name:"NotFound",
        component:lazy(() => import('@/page/error/NotFound'))
      },
      { 
        path: '*',
        component:lazy(() => import('../page/error/NotFound'))
      },
    ]
  },
  { 
    path: '*',
    component:lazy(() => import('../page/error/NotFound'))
  },
]


function ErrorBoundary() {
  let error:any = useRouteError();
  return <div>
    <div>{ error.message}</div>
    <div>{ error.stack}</div>
  </div>;
  return <></>
}

// Route handling
const generateRouter = (routers:any) => {
  return routers.map((item:any) => {
    if (item.children) {
      item.children = generateRouter(item.children)
    }
    item.element = <Suspense fallback={
      <div>Loading...</div>
    }>
      {/* Turn lazy-loaded asynchronous routes into components and load them in */}
      <KeepAlive id={item.name} cacheKey={item.name}>
        <item.component />
      </KeepAlive>
    </Suspense>
    item.errorElement = <ErrorBoundary></ErrorBoundary>
    item.loader = async (res: any) => {
      if (routerLoader && !item.children) {
        if (routerLoader(res,_redirectUrl)) {
          return res;
        } else { 
          return redirect(_redirectUrl);
        }
      }
      return res;
    }
    return item
  })
}

const RouterLoader = (fun: IRouterBeforeLoad) => {
  routerLoader = fun;
}

const Router  = ()=>createHashRouter(generateRouter([...routes]))
export{ Router,RouterLoader}

Three, errorElement, useRouteError

The errorElement attribute in routing is newly added, which accepts a component and displays it when the current routing component has an error. But this function conflicts with the cache component <KeepAlive> component. That is, after using the cache at present, this wrong component cannot be used normally. You can pay attention to whether the author of react-activation has fixed this bug in the future.

The hook useRouteError is newly added and is used to obtain the error message of the component. The combination of the two functions here can make the error message displayed on the page in a custom way, avoiding the blank screen of the entire system page after a component error occurs.

Four, redirect

The redirection hook is currently the only officially provided non-hook control routing hook, which can be used in pure js functions to redirect the routing.

5. Customize the jump hook to realize the routing guard RouterBeforeEach

The official routing jump uses the useNavigate hook. Here we want to implement the routing guard, so I need to make a logical judgment before the routing jump, so I customized a useUtilsNavigate hook for the judgment before the jump. As long as the route jumps through this hook, it can respond to the route guard.

import { NavigateFunction, Location, To, NavigateOptions } from "react-router-dom";
import { RouterLoader } from "@/routes/route";
type IrouterBeforeLoad = (to:Ito,location?: Location) => Boolean;

interface Ito {
  to: To, options?: NavigateOptions
 }

let routerBeforeLoad: IrouterBeforeLoad;
let flag: Boolean = true;

const RouterBeforeEach = (fun: IrouterBeforeLoad) => {
  /// When the page is refreshed, cooperate with the loader to implement the call, and intercept and redirect, and use the flag to judge whether it is the first refresh of the page, so as not to trigger multiple routing checks when calling useUtilsNavigate
  RouterLoader((res: any,redirectUrl:string) => { 
    let result: Boolean=true;
    if (flag) { 
      let url = new URL(res.request.url)
      result = fun({ to: url.pathname })
      if (redirectUrl==url.pathname) { 
        result = true;
      }
    }
    return result;
  })
  routerBeforeLoad = fun;
}

///All js routing jumps through this function, thus doing routing interception
const useUtilsNavigate=(navigate:NavigateFunction,location:Location,to: To, options?: NavigateOptions)=>{
  if (routerBeforeLoad && routerBeforeLoad({ to, options }, location)) {
    //flag setting false flag is not the first time to load the page
    flag = false;
    navigate(to, options)
  } else {
    return;
  }
  //flag setting false flag is not the first time to load the page
  flag = false;
  navigate(to,options)
}

export { useUtilsNavigate, RouterBeforeEach };
export type { IrouterBeforeLoad,Ito };

The RouterLoader function hook is exported in the route definition file, you can see that it is called in the loader attribute in route.tsx. loader is also a new function provided by the new version. It will call back this hook when the component page is loaded. Here I judge whether the page is initially loaded according to the flag. Because if the page is opened directly through the URL, it does not go through useUtilsNavigate, that is, it cannot be used for routing monitoring, so you need to use the loader hook to trigger the routing guard when it is loaded for the first time.

6. Register the routing guard hook at the main entrance of the program

import ReactDOM from 'react-dom';
import {RouterProvider } from "react-router-dom";
import './index.css'
import { Router } from './routes/route';
import { AliveScope } from 'react-activation'
import React from 'react';
import { RouterBeforeEach } from "@/utils/useUtilsNavigate";
RouterBeforeEach(( to,from) => { 
  console.log("route guard to", to)
  console.log("route guard from", from)
  return true;
})

ReactDOM.render(
    <AliveScope>
      <RouterProvider router={Router()}/>
    </AliveScope>,
  document.getElementById('root')
);

Tags: Front-end Vue.js React

Posted by idweb on Wed, 28 Dec 2022 02:31:59 +1030