A simple isomorphic router for Voby.
Heavily inspired by solid-router. For a more direct port of that check out voby-router.
npm install --save voby-simple-router
The following functions are provided, divided between components and hooks.
The following components are some building blocks for your router-aware interface.
The Router
is the most important component, every other component and hook must be called inside a Router
component to work.
import Layout from './pages/_layout';
import Home from './pages/home';
import NotFound from './pages/not_found';
import UserAdmin from './pages/user_admin';
import UserProfile from './pages/user_profile';
import someLoader from './loaders/some';
import {lazy} from 'voby';
import {Navigate, Router} from 'voby-simple-router';
// First of all let's define some routes
// The router is based on Noren (https://github.com/fabiospampinato/noren)
const Routes = {
{ // A regular route, mapping a path to a component
path: '/',
to: Home
},
{ // A redirection route, using the <Navigate> component
path: '/redirect',
to: <Navigate to="/" />
},
{ // A route with children routes
path: '/user',
to: UserAdmin
children: [
{ // A child route with a parameter
path: ':username',
to: UserProfile
}
]
},
{ // A route can also be lazy, for bundle-splitting purposes
path: '/some',
to: lazy ( () => import ( './pages/some' ) ),
// A loader can be specified for each route, which returns some data
// A loader allows for loading in parallel both the route and its data
// The data will be accessible using the "useLoader" hook
// Router hooks can't be called inside a loader, so a context object with some data is provided
loader: ({ params }) => {
return someLoader ();
}
},
{ // A route with a special path, which is matched when no other route matches
path: '/404',
to: NotFound
}
};
// Then let's instantiate our router
// The path to render is not provided, so it's inferred, this is useful when running in the client
const app = (
<Router routes={Routes}>
<Layout />
</Router>
);
// Now let's instantiate a router with a specific path
// The path is provided explicitly, this is mostly useful when doing SSR
const app = (
<Router routes={Routes} path="/user/John">
<Layout />
</Router>
);
// Explicitly use the "path" backend, which is the default
const app = (
<Router routes={Routes} backend="path" path="/user/John">
<Layout />
</Router>
);
// Explicitly use the "hash" backend, which stores the path in the hash part of the URL
const app = (
<Router routes={Routes} backend="hash" path="/user/John">
<Layout />
</Router>
);
// Explicitly use the "memory" backend, which only stores the path in memory
const app = (
<Router routes={Routes} backend="memory" path="/user/John">
<Layout />
</Router>
);
This crucial component renders the actual route that the router selected. If you don't use it your route won'tbe rendered.
import {Route} from 'voby-simple-router';
// Left's render our route in the middle of some arbitrary components
const app = (
<Router routes={Routes}>
<Header />
<Body>
<Route />
</Body>
<Footer />
</Router>
);
This component provides an instant client-side navigation, without making a network request. It's basically an anchor tag that knows about the router.
import {Link} from 'voby-simple-router';
// Let's create a basic navbar
const Navbar = () => (
<ul>
<Link to="/">Homepage</Link>
<Link to="/user">User</Link>
<Link to="/about">About</Link>
<Link to="/foo" replace>Foo+Replace</Link>
<Link to="/foo" state={{ custom: 123 }}>Foo+State</Link>
</ul>
);
This component is like a Link
that clicks itself right when it's rendered. It's useful for redirecting routes.
import {Navigate} from 'voby-simple-router';
// Let's redirect one route to another
const Routes = {
{
path: '/redirect',
to: <Navigate to="/" />
}
};
The following hooks allow you to extract some information from the router.
This hook gives you a resource
to the resolved return value of the loader for the current route.
This hook is not type-safe, you should provide a type for the value as a generic type argument, but it won't be guaranteed to be correct.
import someLoader, {SomeValue} from './loaders/some';
import {lazy, Suspense} from 'voby';
import {useLoader} from 'voby-simple-router';
const Routes = [
{
path: '/some',
to: lazy ( () => import ( './pages/some' ) ),
loader: someLoader
}
];
// Let's get the data from the loader
const App = () => {
const loader = useLoader<SomeValue> ();
const value = () => loader ().value;
return (
<Suspense fallback={<div>Still loading...</div>}>
<p>Value: {value}</p>
</Suspsense>
);
};
This hook tells you the pathname, search, and hash parts of the url the router is currently at.
import {useLocation} from 'voby-simple-router';
// Let's get the current location of the router
const App = () => {
const {pathname, search, hash} = useLocation ();
return (
<>
<p>Pathname: {pathname}</p>
<p>Search: {search}</p>
<p>Hash: {hash}</p>
</>
);
};
This hook allows you to change the pathname the router is at, programmatically.
import {useNavigate} from 'voby-simple-router';
// Let's build a custom <Link> component
const A = ({ href, children }) => {
const navigate = useNavigate ();
const onClick = event => {
event.preventDefault ();
console.log ( `Navigating to: "${href}"` );
// Basic navigation, with history.pushState and no state value
navigate ( href );
// Replace navigation, with history.replaceState, and no state value
navigate ( href, { replace: true } );
// Replace navigation, with history.replaceState, and an arbitrary state value
navigate ( href, { replace: true, state: {} } );
};
return <a href={href} onClick={onClick}>{children}</a>;
};
This hooks allows you to retrieve parameters defined in the currently matched path, e.g. /user/:username
.
import {useParams} from 'voby-simple-router';
// Let's log a parameter
const App = () => {
const params = useParams ();
const username = () => params ().username;
return <p>Current username: {username}</p>;
};
This hooks gives you the currently matched route object, it's relatively low level, but powerful, because you can attach arbitrary data to a route and retriving it this way.
import {useRoute} from 'voby-simple-router';
// Let's log a parameter
const App = () => {
const route = useRoute ();
const someData = () => route ().someData;
return <p>Some custom data: {someData}</p>;
};
This hook allows you to read and write search parameters, which are those encoded in the URL after ?
.
Currently the URLSearchParams
you receive won't react to changes in search parameters unless the entire location changes.
import {useSearchParams} from 'voby-simple-router';
// Let's manipulate search parameters
const App = () => {
const searchParams = useSearchParams ();
const value = () => Number ( searchParams ().get ( 'value' ) ) || 0;
const increment = () => searchparams ().set ( 'value', value () + 1 );
return <p onClick={increment}>Current value: {value}</p>;
};
This low-level hook gives you the raw router, given a list of routes. You might never have to use this directly.
import {useRouter} from 'voby-simple-router';
const Routes = {
{
path: '/',
to: Home
}
// ...
};
// Creating the low-level router
const router = useRouter ( Routes );
// Trying to find a route given a path
router.route ( '/' ); // => { params: {}, route: { path: '/', to: Home } }
router.route ( '/missing' ); // => undefined
The following types are provided.
The type of a route's loader. The loader is called with a context object, since you can't call router hooks inside it.
type RouterLoader<T> = ( ctx: RouterLoaderContext ) => Promise<T>;
The context object passed as argument to each loader.
type RouterLoaderContext = {
pathname: RouterPath,
search: string,
hash: string,
params: RouterParams,
searchParams: URLSearchParams,
route: RouterRoute
};
The type of the location object that useLocation
gives you.
type RouterLocation = {
pathname: ObservableReadonly<RouterPath>,
search: ObservableReadonly<string>,
hash: ObservableReadonly<string>
};
The type of the value that the read-only observable that useParams
gives you contains.
type RouterParams = Record<string, string | undefined>;
The type for the allowed location. Basically it must always start with a /
, for consistency.
type RouterPath = `/${string}`;
The type of a route that you can pass to the router.
type RouterRoute = {
path: string,
to: JSX.Child,
loader?: RouterLoader<unknown>,
children?: RouterRoute[]
};
The type of the low-level router, the one you get with useRouter
.
type RouterRouter = {
route ( path: string ): { params: RouterParams, route: RouterRoute } | undefined
};
MIT © Fabio Spampinato