Awesome
Universal Redux Router
A router that turns URL params into first-class Redux state and runs action creators on navigation.
Navigation
Motivation
If you're a good web citizen each part of your app can be linked to with a URL. These URLs often contain state either as part of the path name or within the query string.
/users/782/posts?page=2&tags[]=coding,making
In the above example, we have the following state:
{
id: 782,
page: 2,
tags: [ 'coding', 'making' ]
}
I want that state in my Redux store!
Features
Extracts state from URLs
Universal Redux Router extracts state from a URL and adds it as first-class state to your Redux store.
It achieves this by allowing you to attach Redux action creators to your routes.
const routes = [
[
'users/:id/posts',
{
id: updateId,
page: updatePage,
tags: updateTags
},
<UsersPosts />
]
]
In the above example, we have one route defined that will
match /users/<anything>/posts
. It has three Redux action
creators attached updateId
, updatePage
and updateTags
.
When a user navigates to a URL, the action creators associated with the matching route are called with the appropriate part of the URL.
For example, navigating to
/users/782/posts?page=2&tags[]=coding,making
will result in
the following function calls:
updateId('782')
updatePage('2')
updateTags([ 'coding', 'making' ])
The returned actions are then used to calculate the new state of the Redux store.
Runs action creators after calculating new state
On top of running action creators to extract state from URL params, Universal Redux Router allows you to define action creators to run after the new state has been calculated.
const routes = [
[
'users/:id/posts',
{
id: updateId,
page: updatePage,
tags: updateTags,
after: [ postsUpdating, getPosts, postsUpdated ]
},
<UsersPosts />
]
]
In the above example the action creators updateId
, updatePage
and updateTags
are run first. The returned actions are used to
calculate the new state.
The after
action creators are then run in sequence, each called
in turn with the updated state.
Handles async action creators
Both URL param action creators and after
action creators can
return promises.
The after
action creators will not be called until all URL
param action creators have resolved and the new state has been
calculated.
Each one of the after
action creators will not be called
until the previous after
action creator has been resolved
and the new state calculated.
Routing on server and client
As the name implies, Universal Redux Router is designed specifically to work the same on both server and client.
No need for environment specific code. Phew!
Examples
- The basic example in the examples directory of this repository.
- The real world example in the examples directory of this repository.
- My React/Redux starter kit.
- My blog.
Installation
npm install universal-redux-router
Usage
Router
The Router
component handles which component will be
displayed by taking the url
property of your Redux store and
your routes array.
It matches a route and returns the component defined within that route.
import { Router } from 'universal-redux-router'
const Root = () => (
<Provider store={store}>
<Router routes={routes} />
</Provider>
)
routerMiddleware
You must use routerMiddleware
in your Redux middleware
stack. This listens for the CHANGE_PAGE_TO
action, matches a
route and then makes a list of additional actions we need to
dispatch.
It also includes a few conveniences like updating scroll position on navigation and handling browser history.
import { routerMiddleware } from 'universal-redux-router'
const middleware = applyMiddleware(routerMiddleware(routes))
return createStore(reducer, state, middleware)
routerReducer
Instead of using Redux's combineReducers
to create your
root reducer, you must use routerReducer
. It has the same
API as combineReducers
.
routerReducer
uses combineReducers
under the hood for all
incoming actions apart from CHANGE_PAGE_TO
. When it receives
CHANGE_PAGE_TO
it iterates over the list of associated
actions to calculate state.
import { routerReducer } from 'universal-redux-router'
const reducer = routerReducer(reducers)
changePageTo
The changePageTo
Redux action creator creates the
CHANGE_PAGE_TO
action. It is how we navigate using Universal
Redux Router.
It can either take an array of data, or a URL string.
import { changePageTo } from 'universal-redux-router'
store.dispatch(
changePageTo([ 'users', id, 'posts', { page, tags } ])
)
store.dispatch(
changePageTo(`/users/${id}/string?page=${page}&tags[]=${tags.join(',')}`)
)
Link
The Link
component is used to create an HTML anchor element
that has changePageTo
handling built in. This means you
don't have to worry about onClick
events or having to
directly call changePageTo
.
Like changePageTo
it accepts both an array of data or a URL
string as its to
prop.
import { Link } from 'universal-redux-router'
<Link to={[ 'users', id, 'posts', { page, tags } ]}>
User posts
</Link>
<Link to={`/users/${id}/string?page=${page}&tags[]=${tags.join(',')}`}>
User posts
</Link>
getState
The getState
helper does exactly what the the router
does on a CHANGE_PAGE_TO
action, but without any dispatch
calls, and therefore without the need for a Redux store.
This means we can use it to calculate the initial state to create our Redux store.
We can also use it on both server and client to avoid passing state between the two.
import { getState } from 'universal-redux-router'
getState(url, routes, reducer).then(state => {
const store = createStore(reducer, state, middleware)
})
Help make this better
Issues and pull requests gratefully received!
I'm also on twitter @colinmeinke.
Thanks :star2:
Thanks
- Henrik Joreteg's article on minimalist routing
- React Router (specifically for the Link API)
- Luke Morton for discussions around routing and Republic
License
ISC.