Router
Provides a <Router />
component that accepts an object definition of routes, conditionally rendering them when the URL matches their path. It also supports nested Routers, a <Link />
component, and a useRouter
hook to interact with the router.
Related components/hooks
Installation
npm install --save @app-elements/router
Usage
import { RouteProvider, Router } from '@app-elements/router'
// import your top-level routes (details about the routes object below)
import routes from './routes'
// ...
// The RouteProvider and top-most Router should both be provided the same
// top-level routes object.
<RouteProvider routes={routes}>
<Router routes={routes} />
</RouteProvider>
Defining Your Routes
export const routes = {
home: {
path: '/',
component: Home
},
users: {
path: '/users',
component: Users
},
user: {
path: '/users/:id',
component: User
},
}
When a path
is matched, the corresponding component
will be rendered. The key for each object (home, users, user), is the name of the route.
Dynamic Values From The URL
If you need to parse the data out of the URL, use a dynamic segment (they start with a :
). The parsed value will become a prop sent to the matched component.
In the above example, {id}
would be a prop on the <User />
component.
Nested Routers
If you want to group certain routes together, you can define multiple Routers. This allows you to, for instance, render a common header or navigation component for a certain grouping of routes. To nest routes, you need to define parent routes. Parent routes look like so:
export default {
marketing: {
routes: marketingRoutes,
component: Marketing
},
account: {
routes: accountRoutes,
component: Account
}
}
You'll notice the difference is that each route object has a routes
property instead of a path
property. If any of the nested routes
match the current URL, then that parent routes' component
will render.
Let's say the accountRoutes
are something like:
export const accountRoutes = {
login: {
path: '/login',
component: Login
},
signup: {
path: '/signup',
component: SignUp
}
}
And the current URL is: /signup
, then the parent route account
will match, and the <Account />
component will render. The last step is to include a <Router />
inside the <Account />
component that gets passed the accountRoutes
object. As an example, <Account />
could look like this:
import accountRoutes from './routes'
// If we wanted to render some navigation links on *all* account routes,
// we would render them inside this `Account` component.
import AccountNav from './AccountNav'
export const Account = () => (
<div>
<AccountNav />
<Router routes={accountRoutes} />
</div>
)
Now you have a top-level router that renders different components based on nested routes. Those top-level, or parent route components can then include a nested <Router />
to gain finer control over what gets rendered based on the current URL.
Router Props
Prop | Type | Default | Description |
---|---|---|---|
routes |
Object | None | An object of objects representing the routes. Supported keys are path , component , and routes . |
StackRouter
<StackRouter />
builds on <Router />
by maintaining a "stack" or history of the rendered components, and exposing that stack to a Function as Child. From there you can determine how you want to manage or render the stack. For instance, by wrapping the current active component/route in the stack with react-transition-group
components, you can easily add animations to your route transitions.
import { StackRouter } from '@app-elements/router'
import { CSSTransition, TransitionGroup } from "react-transition-group"
// ...
{/*
The current active route is always the last in the `stack`
array. In this case, we are going to limit the stack to only
hold 1 route, and just utilize the function as child pattern,
so we can wrap the route component with a TransitionGroup.
*/}
<StackRouter routes={routes}>
{({ stack, limit = 1 }) => {
const { path, args, isBack, Component } = stack[stack.length - 1]
return (
<TransitionGroup className="stack">
<CSSTransition
key={path}
classNames={isBack ? "fade-reverse" : "fade"}
addEndListener={(node, done) => {
node.addEventListener("transitionend", done, false)
}}
>
<div className='page'>
<Component {...args} />
</div>
</CSSTransition>
</TransitionGroup>
)
}}
</StackRouter>
Link
import { Link } from '@app-elements/router'
// Render an anchor with a named route
return <Link to='post' args={{ id: post.id }}>{post.title}</Link>
RouteTo
import { RouteTo } from '@app-elements/router'
// In your component (perhaps when a form success state is reached)
// you can render RouteTo to route to a new URL.
return <RouteTo name='blogPost' args={{ id }} />
Link and RouteTo Props
Prop | Type | Default | Description |
---|---|---|---|
to |
String | None | String that matches a key in your routes object. |
name |
String | None | Same as to , for backwards compatibility/preference. |
args |
Object | None | Object of key-value pairs to replace dynamic values in a route definition. Ex. posts/:id => { id: 1 } |
queries |
Object | None | Object of key-value pairs to convert to querystring params. |
SyncRouterState
import { SyncRouterState } from '@app-elements/router'
// If you wish to sync the router state to your own global state (redux, atom, mobx, etc.)
// {
// route: {
// name: "post",
// path: "/posts/:id",
// args: { id: 1 }
// },
// currentPath: "/posts/1"
// }
return (
<SyncRouterState>
{state => console.log("SyncRouterState", state)}
</SyncRouterState>
)
useRouter
import { useRouter } from '@app-elements/router'
// Access some of the internal functions of the router
const {
path,
setPath,
routeTo,
route,
setRoute
} = useRouter()
useRouter props
Prop | Type | Description |
---|---|---|
path |
String | Current path (defaults to window.location.pathname + window.location.search
|
setPath |
Function | Programmatically set the path |
routeTo |
Function | Perform a "route change", calling history.pushState and setPath
|
route |
Object | The current matched route. Example values |
setRoute |
Function | Programmatically set the route. Only sets if the name does not match current route.name
|
useScrollToTop
We're trying not to make too many assumptions, so the Router does not scroll to top automatically. But, there is a super simple hook to enable such behavior.
import { useScrollToTop } from '@app-elements/router'
// ... in your top-level component
useScrollToTop()
// also returns a ref, if you would like to scroll a specific element to the
// top, rather than the whole window
const ref = useScrollToTop()
<div id="main-nav" ref={ref}>