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

routescape

v1.1.4

Published

Minimalist router for React apps

Downloads

655

Readme

routescape

Minimalist router for React apps

  • Concise API
  • Unopinionated route structure: routes are not necessarily hierarchical or otherwise tightly coupled
  • Same route matching for components and prop values
  • Decoupled from data loading: the router only manages routing
  • Consistent with native APIs:
    • route links are similar to HTML links
    • route navigation interface is similar to window.location
  • SSR-compatible

Installation: npm i routescape

Links and route matching

The route link components <A> and <Area> enabling SPA navigation have the same props as their HTML counterparts: <a> and <area>. Apart from reducing some cognitive load, this allows to quickly migrate from plain HTML links to route links (or the other way around).

import {A, useRoute} from 'routescape';

let App = () => {
    let [route, withRoute] = useRoute();

    return (
        <>
            <nav>
                <A href="/intro" className={withRoute('/intro', 'active')}>
                    Intro
                </A>
            </nav>
            {withRoute('/intro', (
                <main>
                    <h1>Intro</h1>
                </main>
            ))}
        </>
    );
};

The functional route matching by means of withRoute() offers a simple and consistent way to render both components and props based on the current location.

Note that both the intro link's className and <main> are rendered in a similar fashion using the same route-matching function. withRoute('/intro', x) returns x only if the current location is /intro.

(With the component-based route matching adopted by some routers, conditionally rendering a component and marking a link as active via its props have to be handled differently.)

Route matching fallback

Similarly to the ternary operator condition ? x : y (often seen with the general conditional rendering pattern), withRoute() accepts a fallback value as the optional third parameter: withRoute(routePattern, x, y).

let Nav = () => {
    let [route, withRoute] = useRoute();

    return (
        <nav>
            <A
                href="/intro"
                className={withRoute('/intro', 'active', 'inactive')}
            >
                Intro
            </A>
        </nav>
    );
};

In the example above, the link is marked as active if the current location is /intro, and inactive otherwise.

With the third parameter omitted, withRoute('/intro', 'active') results in undefined with locations other than /intro (since the missing fallback parameter is effectively undefined), which is perfectly fine as well.

Navigation mode

By default, after the link navigation occurs, the user can navigate back by pressing the browser's back button. Optionally, by setting data-navigation-mode="replace" a link component can be configured to replace the navigation history entry, which will prevent the user from returning to the previous location by clicking the browser's back button.

Route parameters

withRoute() accepts route patterns of various types: string | RegExp | (string | RegExp)[]. The parameters of a regular expression route pattern (or of the first match in the array) are passed to the second and the third parameter of withRoute() if they are functions.

let App = () => {
    let [, withRoute] = useRoute();

    return (
        <>
            <nav>
                <A href="/intro">Intro</A>
            </nav>
            {withRoute(/^\/section\/(?<id>\d+)\/?$/, ({id}) => (
                <main>
                    <h1>Section #{id}</h1>
                </main>
            ))}
        </>
    );
};

Unknown routes

The fallback parameter of withRoute() is also a way to handle unknown routes:

const routeMap = {
    intro: '/intro',
    sections: /^\/section\/(?<id>\d+)\/?$/,
};

const knownRoutes = Object.values(routeMap);

let App = () => {
    let [, withRoute] = useRoute();

    return (
        <>
            <nav>
                <A href={routeMap.intro}>Intro</A>
            </nav>
            {withRoute(routeMap.intro, (
                <main>
                    <h1>Intro</h1>
                </main>
            ))}
            {withRoute(routeMap.sections, ({id}) => (
                <main>
                    <h1>Section #{id}</h1>
                </main>
            ))}
            {withRoute(knownRoutes, null, (
                <main className="error">
                    <h1>404 Not found</h1>
                </main>
            ))}
        </>
    );
};

Note that the last withRoute() results in null (that is no content) for all known routes and renders the error content for the rest unknown routes.

Although the routes are grouped together in the example above, that's not a requirement. withRoute() calls are not coupled together, they can be split across separate components and files and arranged in any order (like any other conditionally rendered components).

Imperative route navigation

To jump to another route programmatically, there's the route object returned from the useRoute() hook:

let ProfileButton = ({signedIn}) => {
    let [route] = useRoute();

    let handleClick = () => {
        route.assign(signedIn ? '/profile' : '/login');
    };

    return <button onClick={handleClick}>Profile</button>;
};

This particular example is somewhat contrived since it could have been composed in a declarative fashion using the route link component <A>. Still, it demonstrates how the route object can be used in use cases where the imperative navigation is the only reasonable way to go.

The interface of the route object consists of the following parts:

  • SPA navigation via the History API:
    • .assign(), .replace(), .reload(), and readonly properties: .href, .pathname, .search, .hash, semantically similar to window.location;
    • .back(), .forward(), .go(delta), corresponding to the history methods;
  • route matching:
    • .matches(value), checking whether the current location matches the given value;
    • .match(value), returning matched parameters if the given value is a regular expression and null if the current location doesn't match the value.

Location provider

Server-side rendering and unit tests are the examples of the environments lacking a global location (such as window.location). They are the prime use cases for the location provider, <Router>.

Let's consider an express application route as an example:

import {renderToString} from 'react-dom/server';
import {Router} from 'routescape';

app.get('/', (req, res) => {
    let html = renderToString(
        <Router location={req.originalUrl}>
            <App/>
        </Router>,
    );

    res.send(html);
});

The value passed to the router's location prop can be accessed via the useRoute() hook:

let [route, withRoute] = useRoute();

console.log(route.href); // returns the router's `location`

Both route and withRoute() returned from useRoute() operate based on the router's location.

<Router> can be used with client-side rendering as well. In most cases, it is unnecessary since by default the route context takes the global location from window.location if it's available.

Custom routing

The location provider component <Router> can be used to redefine the route matching behavior.

import {Route, getPath, Router} from 'routescape';

export class PathRoute extends Route {
    getHref(location) {
        // disregard `search` and `hash`
        return getPath(location, {search: false, hash: false});
    }
}

let App = () => (
    <Router location={new PathRoute(url)}>
        <AppContent/>
    </Router>
);

By default, routing relies on the entire URL. In this example, we've redefined this behavior to disregard the search and hash portions of the URL.

Extending the Route class gives plenty of room for customization. This approach allows in fact to go beyond the URL-based routing altogether.

Converting plain links to route links

A static chunk of HTML content is an example where the route link component <A> can't be directly used but it still might be desirable to convert plain HTML links to SPA route links. The useRouteLinks() hook can be helpful here:

import {useRef} from 'react';
import {useRouteLinks} from 'routescape';

let Content = ({value}) => {
    let containerRef = useRef(null);

    useRouteLinks(containerRef, 'a');

    return (
        <div ref={containerRef}>
            {value}
        </div>
    );
};

In this example, the useRouteLinks() hook converts all links matching the selector 'a' inside the container referenced by containerRef to SPA route links.