@butterjs/router
v0.0.8
Published
This is the built-in router for Forecastr apps. It takes inspiration from Ruby on Rails, React Router, and Reach Router, but is very opinionated in its own way.
Downloads
7
Readme
Forecastr Router
This is the built-in router for Forecastr apps. It takes inspiration from Ruby on Rails, React Router, and Reach Router, but is very opinionated in its own way.
WARNING: This software is in alpha and should not be considered suitable for production use. In the "make it work; make it right; make it fast" paradigm, RR is in the later stages of the "make it work" phase.
Forecastr Router (RR from now on) is designed to list all routes in a single file, without any nesting. We prefer this design, as it makes it very easy to track which routes map to which pages.
Installation and use outside of a Forecastr app
RR was designed for use in Forecastr apps, and the rest of the documentation here will use examples that are appropriate in that context. That said, you can use RR outside of Forecastr apps too! If you do, there are a few things to note:
- Forecastr auto-imports every Page component in the
Routes.js
file, so if you use it outside of that context, you will need to import your Pages manually.
Router and Route
The first thing you need is a Router
. It will contain all of your routes. RR will attempt to match the current URL to each route in turn, stopping when it finds a match, and rendering that route only. The only exception to this is the notfound
route, which can be placed anywhere in the list and only matches when no other routes do.
Each route is specified with a Route
. Our first route will tell RR what to render when no other route matches:
// Routes.js
import { Router, Route } from '@forecastr/router'
const Routes = () => (
<Router>
<Route notfound page={NotFoundPage} />
</Router>
)
export default Routes
RR expects a single Route
with a notfound
prop. When no other route is found to match, the component in the page
prop will be rendered.
To create a route to a normal Page, you'll pass three props: path
, page
, and name
:
// Routes.js
<Route path="/" page={HomePage} name="home" />
The path
prop specifies the URL path to match, starting with the beginning slash. The page
prop specifies the Page component to render when the path is matched. The name
prop is used to specify the name of the named route function.
Link and named route functions
When it comes to routing, matching URLs to Pages is only half the equation. The other half is generating links to your pages. RR makes this really simple without having to hardcode URL paths. In a Page component, you can do this (only relevant bits are shown in code samples from now on):
// SomePage.js
import { Link, routes } from '@forecastr/router'
// Given the route in the last section, this produces: <a href="/">
const SomePage = () => <Link to={routes.home()} />
You use a Link
to generate a link to one of your routes and can access URL generators for any of your routes from the routes
object. We call the functions on the routes
object named route functions and they are named after whatever you specify in the name
prop of the Route
.
Named route functions simply return a string, so you can still pass in hardcoded strings to the to
prop of the Link
component, but using the proper named route function is easier and safer. Plus, if you ever decide to change the path
of a route, you don't need to change any of the Link
s to it (as long as you keep the name
the same)!
Route parameters
To match variable data in a path, you can use route parameters, which are specified by a parameter name surrounded by curly braces:
// Routes.js
<Route path="/user/{id}>" page={UserPage} name="user" />
This route will match URLs like /user/7
or /user/mojombo
. You can have as many route parameters as you like:
// Routes.js
<Route path="/blog/{year}/{month}/{day}/{slug}" page={PostPage} name="post" />
By default, route parameters will match up to the next slash or end-of-string. Once extracted, the route parameters are sent as props to the Page component. In the 2nd example above, you can receive them like so:
// PostPage.js
const PostPage = ({ year, month, day, slug }) => { ... }
Named route functions with parameters
If a route has route parameters, then its named route function will take an object of those same parameters as an argument:
// SomePage.js
<Link to={routes.user({ id: 7 })} />
All parameters will be converted to strings before being inserted into the generated URL. If you don't like the default JavaScript behavior of how this conversion happens, make sure to convert to a string before passing it into the named route function.
Route parameter types
Route parameters are extracted as strings by default, but they will often represent typed data. RR offers a convenient way to auto-convert certain types right in the path
specification:
// Routes.js
<Route path="/user/{id:Int}" page={UserPage} name="user" />
By adding :Int
onto the route parameter, you are telling RR to only match /\d+/
and then use Number()
to convert the parameter into a number. Now, instead of a string being sent to the Page, a number will be sent! This means you could have both a route that matches numeric user IDs and a route that matches string IDs:
// Routes.js
<Route path="/user/{id:Int}" page={UserIntPage} name="userInt" />
<Route path="/user/{id}" page={UserStringPage} name="userString" />
Now, if a request for /user/mojombo
comes in, it will fail to match the first route, but will succeed in matching the second.
Core route parameter types
We call built-in parameter types core parameter types. All core parameter types begin with a capital letter. Here are the types:
Int
- Matches and converts an integer.
User route parameter types
RR goes even further, allowing you to define your own route parameter types. Your custom types must begin with a lowercase letter. You can specify them like so:
// Routes.js
const userRouteParamTypes = {
slug: {
constraint: /\w+-\w+/,
transform: (param) => param.split('-'),
}
}
<Router paramTypes={userRouteParamTypes}>
<Route path="/post/{name:slug}" page={PostPage} name={post} />
</Router>
Here we've created a custom slug
route parameter type. It is defined by a constraint
and a transform
. Both are optional; the default constraint is /[^/]+/
and the default transform is (param) => param
.
In the route we've specified a route parameter of {name:slug}
which will invoke our custom route parameter type and if we have a requst for /post/forecastr-router
, the resulting name
prop delivered to PostPage
will be ['forecastr', 'router']
.
useParams
Sometimes it's convenient to receive route parameters as the props to the Page, but in the case where a deeply nested component needs access to the route parameters, it quickly becomes tedious to pass those props through every intervening component. RR solves this with the useParams
hook:
// SomeDeeplyNestedComponent.js
import { useParams } from '@forecastr/router'
const SomeDeeplyNestedComponent = () => {
const { id } = useParams()
...
}
In the above example, we've pulled in the id
route parameter without needing to have it passed in to us from anywhere.
navigate
If you'd like to programmatically navigate to a different page, you can simply use the navigate
function:
// SomePage.js
import { navigate, routes } from '@forecastr/router'
const SomePage = () => {
const onSomeAction = () => {
navigate(routes.home())
}
...
}