<Router/>
Next-generation router for React tailored towards complex SPA use-cases, makes your application feel like it's been rendered on the server-side.
Source code is hosted on GitHub
A demo can be found on CodeSandbox
This project was heavily inspired by react-router and a lot of attention has been paid to keep the API very similar, therefore the transition will be quick and hassle-free. This router implementation offers many convenient features that are usually too hard to implement in regular single-page applications. All of these features are on-demand, if you don't need them, don't use them. Out of the box, this router behaves exactly like the react-router. By adding some additional flags, your app starts to feel totally different. The UX increases drastically, your routes feel like they are being rendered on the server-side.
Declarative routing inside React
Nested routing
Async loading of components
Supports route loaders
Supports route unloaders
Routes feel like they've been rendered on the server
Controlled mode for premature rendering
Hooks for various use cases
Easy URL query manipulation
Switch, Redirect, Link, and other goodies
Uses history (v5) for maximal compatibility with other libraries
Philosophy
This router emerged out of necessity to solve common SPA needs that are incredibly hard / almost impossible to solve without granular control over the router and its transitions. This is why this is not an addon on top of react-router, nor is it a fork. A totally new approach was needed to solve many problems in a developer-friendly way. To fully understand the reasons, let's have a look at the problems every single page application has to live with.
Code splitting
Imagine you take advantage of code splitting and lazy load your route implementation upon first navigation.
To achieve that, you have to wrap your route component into another component to display some sort of a loading overlay. As soon as the implementation has been loaded, you render the actual page.
For a brief moment, you will have to show a loading overlay to indicate the progress. Users will no longer see the previous screen and just stare and the loading overlay until the new route is ready to render. You can immediately tell that this is a SPA based on the bad UX.
Router solves this problem by fetching your route implementation behind the scenes, without unmounting the current route. You can show a loading bar on the top of the page in the meantime. Users will see the previous route plus the loading bar until the new route is ready for the transition. This feels more natural and is also what Google and Facebook do in their applications.
Route loaders
Imagine a route that renders a table with data, where data is fetched from the server through an API call.
To achieve that, you need to display a global loading overlay until the data has been loaded. You need to manually orchestrate the loading overlay if there are multiple components that you have to wait for. Another trick that many SPAs fall back to is the use of skeletons - you render some placeholders that indicate that there is more to come, but the view is not fully ready yet.
For a brief moment, you have no data to show and your users have either to stare at the loading overlay or at some placeholders without any meaningful content until the page is fully ready to render.
Router solves this problem by allowing every component to register a loader. It will wait for all loaders to fully resolve prior to doing the transition. You can show a progress bar at the top of the page, your users never have to stare on an empty screen or at placeholder components. Preloaders can be freely registered from any component that is being rendered inside the new route. You don't need to orchestrate anything manually. Adopting this pattern is a matter of minutes.
Route loaders can also return an unloader that is triggered when the route is being navigated away from.
Route unloaders
Imagine your current route triggers an action that opens a modal. Inside that modal, you have a link to another route. By clicking that link, the modal disappears immediately as the transition begins.
Your components don't have the means to do anything prior to transitioning away. The transition starts immediately, the modal simply disappears and new content is shown. It would feel more natural if the modal would gracefully close, maybe with a nice animation, just before the new route is rendered.
Router solves this problem by allowing every component to register an unloader. It will wait for all unloaders to fully resolve prior to doing the transition. You can do whatever is necessary, like sending some data to the server, running animations, etc. Unloading of the current route is part of the route transition life cycle, therefore you can show a progress bar at the top of the page indicating users that the next route is still being loaded.
Controlled mode
Imagine you try to load another route that needs to fetch some data. Wouldn't it be cool, if you could show a small overlay, covering the currently rendered screen, and providing some additional information to the users, something like "Preparing the rocket to launch", "Sending out the droids", etc.?
This can be achieved thanks to the controlled mode. This mode can be applied to a single route or to all routes at once. This mode allows a route to render content prior to it being fully ready to render / fully loaded.
Router achieves this behaviour by having two routes mounted at the same time, while contrary to the un-controlled mode, the route that is still being in transition will not be hidden, it will be visible side by side with the previous screen. Now it's up to you to decide what you want to show and when. Thankfully there are many convenient hooks that make dealing with the life cycle a piece of cake.
Life cycle
Let's have a look at what the typical router/route life cycle looks like:
Mount router
Mount routes
Register routes with router
Figure out active routes
Load route implementations if necessary
Mount new routes in hidden mode, render normally in controlled mode
Wait for route loaders to complete
Wait for route unloaders to complete
Unmount the previous routes
Show new routes
If any new routes are registered during the life cycle execution, the router will incorporate them in the transition. While performing transitions, users can be presented with a progress indicator, but they will never lose the currently rendered screen. You don't need to show any skeletons while fetching relevant data. Each route feels like it has been rendered on the server.
Summary
As you can see, this router implementation is incredibly powerful and flexible. You can have regular routes side by side with routes that preload content, unload gracefully, allow children components to delay transitions while async operations are being executed, render multiple routes side by side in the controlled mode, etc. You can freely mix and match different behaviors based on your requirements.
Quick start
Basic router setup with some routes:
Patch matching
Paths can be matched in a regular or an exact mode. Path parameters can be matched in different ways using the additional ?
, +
and *
modifiers.
Exact mode
Match path in the exact mode:
Path parameters
Match path parameters that can be retrieved later:
Zero or one
Match zero or exactly one path segment:
One or many
Match one or multiple path segments:
Zero or many
Match zero or multiple path segments:
Path parameters
Parameters can be accessed inside a route using the useParams()
and useRoute()
hooks:
Conditional routes
Due to the nature of how this router implementation works, it is considered a bad practice to mount and unmount routes conditionally:
Manually unmounting a route bypasses it's unloaders
Conditionally mounting routes inside a
<Switch />
component might lead to unexpected behaviour
Don't panic, we've got you covered! You can use the <Group />
, <Switch />
or <Route />
components whenever you plan to toggle available routes at runtime:
<Router />
All routes must be wrapped inside this component:
Base path
Define a base path for the router:
Loaders and unloaders
Enable loaders and unloaders for all routes:
Enable loaders and unloaders for a specific route:
Loader and unloader threshold
Define how long the router waits for loaders and unloaders to register, default is 5ms
:
Debugging
Enable detailed life cycle logs:
Custom history
Pass a custom history instance, this is useful for testing or when working with other libraries:
Testing
During the tests you might want to provide a history instance pointing to a specific location:
<Route />
The Route component is used to connect your components to a specific location path:
Render function
Provide a custom render function:
Render component
Provide a component to render:
Async component
Load component implementation asynchronously:
Async render function
Provide a render function with some async logic:
Route path
Specify a path for the route:
Route without a path will always match:
Exact path
Match route path in the exact mode:
Absolute path
When nesting routes, you might want to provide an absolute route path instead of the relative one:
Nested routes
Routes can be freely nested inside each other, parent route's path is automatically used as the path prefix:
Loaders
You can add a loader by using the useRouteLoader()
hook:
You have to set the loadable property on the route or the router to enable this feature. Make sure to also set the unloadable property on the route or the router if you want to return an unloader from the useRouteLoader hook.
Unloaders
You can add an unloader by using the useRouteUnloader()
hook.
You have to set the unloadable property on the route or the router to enable this feature.
<Link />
Navigate to another route by clicking a link.
Active links
Links that match the current path, have the property data-active
set to true
. You can change the styling of active links using this simple CSS rule:
Exact links
You can narrow down what paths will be considered as matching, by setting the exact
property. Check the path matching section for more details.
Modifier keys
By default, links respect the ctrl+click
, alt+click
and cmd+click
events as well as the target
property. Links clicked using a modifier key will use the default browser behavior instead of triggering the normal navigation. This can be disabled by setting the intercept
property to false
:
<Switch />
This component will render the first route that matches the current path, it accepts the same props as the <Group/>
component:
<Group />
This component can be used to apply specific settings to all of its child routes. The disabled
flag is especially useful whenever you need to disable some routes without having to unmount them:
<Redirect />
This component will trigger a redirect to another path upon render:
useRouter()
Retrieves router handle from the context, it exposes some useful methods and properties:
Detect loading
Check if the router is loading anything anywhere on the page, same as the useRouterIsLoading()
hook:
Detect unloading
Check if the router is unloading anything anywhere on the page, same as the useRouterIsUnloading()
hook:
Detect visibility
Check if the router is showing anything anywhere on the page, same as the useRouterIsVisible()
hook:
Redirects
You can use the router instance to redirect to another path, same as the useRedirect()
hook:
useRoute()
Retrieves a route handle from the context, it exposes some useful methods and properties:
Detect loading
Check if the route is loading, same as the useRouteIsLoading()
hook:
Detect unloading
Check if the route is unloading, same as the useRouteIsUnloading()
hook:
Detect visibility
Check if the route is visible same as the useRouteIsVisible()
hook:
Redirects
Route handle can be used to redirect to another path, same as useRedirect()
hook:
Parameters
Route parameters can be accessed directly through the route handle, same as the useParams()
hook:
Query
Route query can be accessed directly through the route handle, there is also the useQuery()
hook that can also be used to modify the query:
Status
Route status can be accessed directly through the route handle, same as the useRouteStatus()
hook:
useRoutes()
Returns an object with all the routes that were detected by the router:
useRouteLoader()
Route loaders can be used to preload data prior to triggering the route transition:
Make sure to also set the unloadable property on the route or the router if you want to return an unloader from the useRouteLoader hook.
You can also create a route loader without the callback and resolve it manually:
Check if this particular loader is running:
useRouteUnloader()
Route unloaders can be used to run some logic prior to the route is being unmounted:
You can also create a route unloader without the callback and resolve it manually:
Check if this particular unloader is running:
useRouteStatus()
Get status of the current route:
useRouterIsLoading()
Check if the router is loading anything anywhere on the page:
useRouterIsUnloading()
Check if the router is unloading anything anywhere on the page:
useRouterIsVisible()
Check if the router is showing anything anywhere on the page:
useRouteIsLoading()
Check if the route is loading:
useRouteIsUnloading()
Check if the route is unloading:
useRouteIsVisible()
Check if the route is visible:
useHistory()
Returns history instance from the context:
useLocation()
Returns current location object:
useMatch()
Matches current location against the given pattern:
useParams()
Retrieve route params:
Retrieve with default parameters:
useQuery()
Retrieve a query handle that can be used to read and write data into the query part of the URL. By providing default values for each query part, you define a list of query fields that you want to control. You won't be able to read or modify query parts that are not part of this list. This allows different components to work with different parts of the query without having to worry about each other's reads and writes:
You can update a part of the query, and preserve the previous values, this operation will never modify query parts that are not part of the list with the default values:
You can override the whole query, query parts that were omitted will be stripped from the final query, this operation will never modify query parts that are not part of the list with the default values. Omitted query pieces will be replaced with the default value:
useRedirect()
Retrieve a redirect function:
Redirect with query:
Redirect with hash:
Redirect and preserve query from the current URL:
Redirect and preserve hash from the current URL:
usePathWithBase()
Create a path according to the base path that has been configured on the router:
You can specify a custom base path:
useQueryStringifier()
Retrieve a helper to turn any object into a URL query string:
The ? character is not part of the final query, you have to add it manually.
useQueryParser()
Retrieve a helper to parse a URL query string:
Last updated