A sane React Routing library for the frontend
MIT License
A sane front-end Routing library for React Github: https://github.com/iyobo/react-spoon
npm install --save react-spoon
in html
<div id='app'></div>
In main file:
import React from 'react';
import {ReactSpoon} from 'react-spoon';
const SomeProvider = {...} //Redux, MobX or any provider
const store = {...} //any props the provider needs
new ReactSpoon([
{
name: '',
path: '*',
handler: AppLayout,
children: [
{ path: '', redirectTo: 'dashboard' },
{ path: 'dashboard', name: 'dashboard', handler: DashboardPage },
{ path: 'about', name: 'about', handler: () => <h1>About</h1> },
{
path: 'models/:modelName*', name: 'models', handler: ModelLayout, children: [
{ path: 'models/:modelName', name: 'models.list', handler: ModelListPage },
{ path: 'models/:modelName/create', name: 'models.create', handler: ModelEditPage },
{ path: 'models/:modelName/:id', name: 'models.edit', handler: ModelEditPage }
]
}
]
}
],
{
domId: 'app',
providers: [
{component: SomeProvider, props: { store }},
{component: AnotherProvider}
]
});
You are also able to have multiple ReactSpoon instances on a page with multiple anchor points/domIds.
Its signature: new ReactSpoon(routeTree, opts)
<SomeProvider {...store}> // ... your routed components
These have been replaced by opts.providers and are now decommisioned. Please upgrade by using the new opts.providers syntax:
* opts.provider: The component class declaration or equivalent of a provider to wrap your app with (In the future, this will alternatively be an array for nesting multiple provdiders)
* opts.providerProps: A prop map of attributes/properties to attach to the equivalent provider. (In the future, this will alternatively be an array of prop maps)
A layout rendering nested pages is really just rendering children.
import {Component} from 'react';
class AppLayout extends Component {
render(){
...
<div>
{this.props.children}
</div>
...
}
}
import {Link} from 'react-spoon';
...
//This will have class="active" when we are on the route named "dashboard".
//
<Link toName="dashboard">
<p>Dashboard</p>
</Link>
//Passing params to named Routes
<Link toName="models.list" params={{modelName: 'User'}} >
<p>List All Users</p>
</Link>
<Link toName="models.edit" params={{modelName: 'Role', id: 'abc123'}} >
<p>Edit Role</p>
</Link>
Every instance of React-Spoon makes use of the react context API for making itself visible in any component. You grab a reference to this instance by statically defining contextTypes in your component:
class MyComponent extends React.Component{
...
static contextTypes = {
router: PropTypes.any
}
...
someFunction = () => {
//navigates to destination
this.context.router.go('app.myRouteName', {routeParams} )
//or if you just want the url without actually navigating to it
const path = this.context.router.buildLink('app.myRouteName', {routeParams} )
}
It is highly recommended to navigate this way as opposed to just trying to change window.location.href... (even though that should still work).
Also, only named routes can be programmatically navigated to at this time. (It's a better pattern/structure to navigate with named routes anyway!)
Spoon will look for a static OnEnter(props) function declaration in your React Component and call it whenever it is navigating to that component. This respects nested routing too, with the topmost component's onEnter triggered first and the next in sequence. (First to Last) In the future, this function might be made to handle (returned) promises.
class DashboardPage extends React.Component{
...
static onEnter(props){
console.log('Entering Dashboard Page');
}
...
}
If you'd like an onLeave(...) hook, create an issue on github. I am actively using RSpoon for open-source dev so chances are I'll add that before you create it. Race ya :P
Beat you to it! :D
You can now create a static onLeave(props) function that RSpoon will call whenever it is navigating away from a route's component. This, like onEnter, respects nested routing. Only difference is that it reverses the order of activation, i.e. Last to First.
class DashboardPage extends React.Component{
...
static onLeave(props){
console.log('Leaving Dashboard Page');
}
...
}
Use these to store and retrieve values or objects from the URL. It should not be used for persistence, but more so as a way for your users to be able to return to a very particular/predictable state of your app by using the exact same URL. e.g See what Google Maps does to it's URL while you navigate. Then try copy that URL in some other tab (or send it to a friend ) and see how it takes you back to that exact map center/zoom state.
import {storeState} from 'react-spoon';
var myState = {
paging: {
page: 1,
limit: 10
},
sort: { dateCreated: -1 },
filters: []
}
}
storeState('modelFilter', myState);
Simple! You'll notice your URL changes after you call this function: e.g #/models/Foo**@{"modelFilter":{"paging":{"page":1,"limit":10},"sort":{"dateCreated":-1},"filters":[]}}**
The First '@' is the delimeter that seperates your path from your data. The data is stored as a JSON string.
You'll want to do this, for example, when the state you are trying to persist in your url changes
Retrieving state is just as easy
import {getState} from 'react-spoon';
...
myState = getState('modelFilter');
And now myState is chuck full of all that state that is currently in your URL.
Clearly, You probably want to do this when loading/mounting/onEntering something that needs this state.
JSON is more flexible to configure than XML JSX :D.
For one, It's simply the best vehicle for conditional or even distributed route configuration.
You can have different modules/functions/whatever define what they want for a route and it can all then be converged into one JS object that RSpoon consumes.
So while building JollofJS, I found that the routing libraries in the React ecosystem just didn't seem to get what routing libraries do IMO. They either had too much ceremony around actually using them, or they kept stripping themselves of useful features with every major release (fascinating right?). I also remember the devs of one claiming that "Named routes are an anti-pattern". :P
Anyway! It became clear that if I was ever going to continue onwards without having to keep revisiting this routing issues, I'd just have to grab the bull by the horns and create one that worked "for me". I'm sharing it in hopes that it works for someone out there too.
I created React-Spoon while creating JollofJS (Think Django for NodeJS - Still in Development). I created its built-in admin tool with React. JollofJS is named after Jollof Rice, a delicious Nigerian rice dish. You'd usually use a spoon when serving rice... which made React-Spoon an appropriate router name.
...Yes, I'm a Foodie :P
Because React's life-cycle functions can be unpredictable, especially when doing nested routing. Say you wanted to print the name of each page the router was rendering in the EXACT NESTED ORDER each time you route there i.e AppLayout > ModelLayout > ModelListPage ,
You may find that they don't show up in this order or sometimes don't show up at all. Sometimes, one route will show up multiple times while others never do.
Defining/using the onEnter hook just gives you one solid way to reliably control your react app's page-nesting sequencing with improved precision. And it is WAY more cleaner and elegant to define this static hook at the class/component level, and not in your main file or wherever you are defining the route tree.
Besides wanting an elegant, full-featured frontend routing library...needing one with Reliable, Object-Level route-event handling was one of the key reasons why I created React-Spoon
npm i
Make sure babel is installed globally
Build with npm run build
;