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

@finalapp/react-safe-router

v0.6.1

Published

**Routing extensions and type safety for `react-router` and `history` packages**

Downloads

15

Readme

React safe router

Routing extensions and type safety for react-router and history packages


Installation

yarn add @finalapp/react-safe-router

or

npm i @finalapp/react-safe-router

History

We are extending history package with useful helpers:

  • withBlocker - to gain more control over blocker functionality - we can block routing completely anywhere inside our code (not only inside react components tree). Also provide surpassBlocker flag inside navigate.state object to navigate anyway even if we have active blocker.
  • withPreserveQuery - flag to indicate if next route shouldn't clear query params. Instead of flag we can pass a function which gain acces to query and can return any query string.
  • withPreviousRoute - to know on which route user was before the current one.

Routing

  • Type safety! - if you initialize routing with createRouting helper - you get overriden navigate, Link, Navigate functions from react-router wich are type safe and will help you with navigation, refactorings etc.
  • isRoute | useIsRoute - type safe function to check if current route matches to route name which you will provide as an argument
  • middleware field inside routing config object - run middleware functions which can check anything and return differet component or navigate component to redirect the user
  • rest functions are just overrides from react-router with changed arguments to be route name which you will provide with configuration
  • custom searchString parser - you can provide deep nested values as search params, also you get hooks to get current search params

Get started

In index.tsx

...
import { HistoryRouter } from "@finalapp/react-safe-router";
import { Router } from "./Router";
...


render(
  <HistoryRouter>
    <Router />
  </HistoryRouter>
  document.getElementById("root"),
);

In Router.tsx

...
import { RouteConfig, RouteValue, createRouting } from "@finalapp/react-safe-router";
...

type BasePaths = {
  HOME: RouteValue;
  "404": RouteValue;
  USERS: RouteValue<{ params: { id: string }, search: { isFriend: boolean }, state?: { isABTest: boolean } }>;
};

const isLogged = () => true;
const hasPermission = (permission: "USER" | "ADMIN") => () => true;

const BASE: RouteConfig<BasePaths> = {
  HOME: {
    path: "/",
    Component: PageHome,
  },
  "404": {
    path: "/404",
    Component: Page404,
  },
  "USERS: {
    path: "/users/:id",
    Component: PageUsers,
    middleware: [isLogged, hasPermission("USER")]
  }
};

export type Paths = BasePaths;

export const {
  navigateTo,
  Navigate,
  getMatchRoutes,
  Link,
  NavLink,
  getPath,
  getPathGlobal,
  getRoutingComponent,
  isRoute,
  useIsRoute,
  useNavigateTo,
} = createRouting<Paths>();

export const Router = getRoutingComponent(BASE)((getConfig) => [
  getConfig("HOME"),
  getConfig("USERS"),
  getConfig("404"),
]);

API createRouting

navigateTo - navigation inside application. To use outside of react tree. Uses history functions under the hood

import { navigateTo } from "./Router";

navigateTo({ route: "HOME" });
navigateTo({ route: "USERS", params: { id: "1" }, search: { isFriend: false } }); // typescript will complain if we wont provide needed arguments provided in `BasePaths` type

useNavigateTo - uses navigateTo but instead of direct history functions - uses react-router - useNavigate. Its convinient to use it because it hase more checks to avoid navigate to same routes etc.

import { useNavigateTo } from "./Router";

useNavigateTo({ route: "HOME" });

Navigate - React component to make redirect.

import { Navigate } from "./Router";

<Navigate route="HOME" />;

getMatchRoutes - get matching routes configs for the current route or previous one.

import { getMatchRoutes } from "./Router";

getMatchRoutes({ previousRoute: true });

// to match current route
getMatchRoutes({ pathname: window?.location?.pathname });
or;
getMatchRoutes(); // it is the same

Link - React base component to make links.

import { Link } from "./Route";

<Link route="HOME">Go to home page</Link>;
<Link route="USERS" state={{ isABTest: true }} params={{ id: "2" }} search={{ isFriend: false }}>
  User info
</Link>;

NavLink - Same as Link but check if link route is current one and then add active className if it is

import { NavLink } from "./Route";

// if current route is HOME - then link element has `actvive` className
<NavLink route="HOME">Go to home page</Link>;

getConfig - To be used inside getRoutingComponent, provide route name and it will parse it to RouteObject used isnide react-router. If your route is nested - just nest it with getConfig also. Nesting is second argument - array of getConfig calls.

import { getConfig } from "./Route";

getConfig("HOME");
getConfig("HOME", [getConfig("HOME_NESTED")]); // with nested routes

getPath - Get path for requested route, can pass to it search and query params

import { getPath } from "./Route";

const path = getPath("HOME");

getPathGlobal - uses getPath but return path with location part, second argument can be omitted then default will be window.location.origin

import { getPathGlobal } from "./Route";

const path = getPathGlobal("HOME", "http://site.com"); // custom origin
const path = getPathGlobal("HOME"); // witout second parameter - default `widnow.location.origin` will be used

isRoute and useIsRoute - check if provided route name is current or previous one

import { isRoute } from "./Route";

// useIsRoute has same arguments as isRoute
// one difference is it should be used inside components when you want to sync checking with navigation change
isRoute("HOME"); // check if current route is `HOME`
isRoute("HOME", true); // with flag as second argument - will check if previous route was `HOME`
isRoute(["HOME", "USERS"]); // check for multiple routes is any of them is current one

getRoutingComponent - Get root routing caomponent to render inside main react component. It will create Routes for application

export const Router = getRoutingComponent(PATHS)((getConfig) => [getConfig("HOME"), getConfig("404")]);

API history

its olny give few more things on top of history package

withPreviousRoute - give you access to previous route, you can also clear previous route eg after user logs out

import { history } from "./Router";

type PreviousRoute = null | Pick<BrowserHistory, "location" | "action">;

const previous: PreviousRoute = history.getPreviousRoute();
history.clearPreviousRoute(); // clear previous route

withPreserveQuery - with that while navigating with any navigation function/component - pass state flag preserveQuery to dont clear query on route change or return some custom query. Its convinient to use eg after user logs in with some query string and we want to preserve it one log redirect

import { history, useNavigateTo, qs } from "./Router";

type PreserveQuery = true | ((currentSearch: string) => string);
type PreserveQueryState = { preserveQuery?: PreserveQuery };

navigateTo({ route: "HOME", state: { preserveQuery: true } }); // dont clear current query on route change
navigateTo({
  route: "HOME",
  state: {
    preserveQuery: (query) => qs.serialize({ custom: { nested: true } }), // function to return some different query
  },
});

withBlocker - create navigaton blocking functionality with more control over react-router - useBlocker

import { history, useNavigatonBlocker } from "./Router";

type HistoryBlockerState = { surpassBlocker?: boolean };
type BlockerArg = string | (() => boolean);

const unblock = history.blockNavigation(arg: BlockerArg); // block navigation, provide custom blocker function wich can test for some info after user tries to change route, or just a string which will use native window.confirm and you will must inBlock it bu yourself

// also can be used as react hook, `when` arg change can unblock routing automatically
const unblock = useNavigatonBlocker(arg: BlockerArg, when: boolean)

API searchString

Create small zustand store to cache deserialized current search string. also give you few methods to get and update it

qs - helpers to serialize and deserialize search string. Ir deserialize numbers, undefined, null correctly, not in string form!

import { qs } from "./Router";

const searchString = qs.serialize({ obj: { a: 1 } });
const searchObject = qs.deserialize("obj.a=1");

useSearchParams - zustand store, also returns 2 react ooks to get search params

type SearchParamsState = {
  // current search string deserialized
  search: Record<string, any>;
  // current search string
  searchString: string;
  // get one particular params from search string by path
  getParam: (path: string, state?: SearchParamsState) => any;
  // get all search params or select needed by selector function
  getParams: (selector?: <T = any>(search: SearchParamsState["search"]) => T, state?: SearchParamsState) => any;
  //  updating search string
  updateSearch: (
    params: SearchParamsState["search"],
    merging?: "replace" | "merge" | "deepMerge",
    routeOption?: "replace" | "push",
  ) => void;
};

import { useSearchParamsSelector, useSearchParamsGetByPath, useSearchParams } from "./Router";

useSearchParams.getState(); // returns all state typed above
const param = useSearchParams.getState().getParam("object.param1");
const params = useSearchParams.getState().getParams((params) => params.object?.param1 || null);
useSearchParams.getState().updateSearch(
  { object: { param2: "new" } },
  "deepMerge", // as you can see in above type - you have 3 posibilities, replace qith new one, shallow merge or deep merge
  "replace", // default is `push`, decide what of a route change you want, replace current one or push to new
);

// hooks
const param1 = useSearchParamsGetByPath("object.param1");
const params = useSearchParamsSelector(); // all params
const paramsSelected = useSearchParamsSelector((p) => ({ param1: p.object.param1, param2: p.object.param2 }));