universal-redux-router

A router that turns URL params into first-class Redux state and runs action creators on navigation

Usage no npm install needed!

<script type="module">
  import universalReduxRouter from 'https://cdn.skypack.dev/universal-redux-router';
</script>

README

Universal Redux Router

A router that turns URL params into first-class Redux state and runs action creators on navigation.


Test status Dependencies status

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

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

License

ISC.