@touchtribe/redux-router
v1.8.0
Published
Bare basic react router for usage with redux
Downloads
3
Readme
@touchtribe/redux-router
redux-router
is a basic router for Redux applications.
While React Router is a great library, it makes it difficult to combine it with Redux patterns and near impossible to integrate it with Redux middlewares.
The router is inspired by redux-little-router. To understand the reasoning behind most of this router, check: "Let the URL do the Talking": Part 1 Part 2 Part 3
The principles behind the router are:
- The URL is just another member of the state tree
- URL changes are just plain actions
- Route definitions should be easily customizable without having an impact on the actual implementation
- Route definitions should be reusable throughout the app
- Route definitions should come from a single source of truth
Usage
The router uses:
- a
middleware
for:- intercepting navigation actions and calling their equivalent method in 'history'
- intercepting URL changes, parsing them based on the specified
patterns
and dispatchinglocationChanged
actions
- a
reducer
for updating the state on URL changes patterns
for parsing the URL and translating it into a meaningful state-update- a
Route
component which renders children based on current route - a
Link
component which dispatch navigation actions when clicked
Middleware
createMiddleware(history, patterns)
The middleware is responsible for:
- intercepting navigation actions and calling their equivalent method in
history
- intercepting URL changes and dispatching
locationChanged
action
Paremeters:
history
is one of the types from thehistory
librarypatterns
is either- an array of patterns
- an object having patterns as values
import { createStore, applyMiddleware } from 'redux'
import { createBrowserHistory } from 'history'
import { createMiddleware, createPattern } from '@touchtribe/redux-router'
import rootReducer from './reducers'
const history = createBrowserHistory()
const patterns = {
Home: createPattern('/'),
Contact: createPattern('/contact')
}
const routerMiddleware = createMiddleware(history, patterns)
const store = createStore(
rootReducer,
{},
applyMiddleware(routerMiddleware)
)
Often, you'll want to update state or trigger side effects after loading the initial URL to ensure compatibility with other store enhancers.
import { initializeLocation } from '@touchtribe/redux-router'
// ...after creating your store and initializing other side-effect handlers
store.dispatch(initializeLocation(history.location))
Reducer
The router reducer works on the namespace router
, which can also be access through routerReducer.toString()
or String(routerReducer)
.
import { combineReducers } from 'redux'
import { routerReducer } from '@touchtribe/redux-router'
const rootReducer = combineReducers({
router: routerReducer
})
// or using the routerReducer.toString() method
const rootReducer = combineReducers({
[routerReducer]: routerReducer
})
Patterns
Patterns are definitions for URLs, which gets parsed using url-pattern
let pattern = createPattern('/users/:id')
See the documentation of url-pattern
on how to use them.
React bindings and usage
<Route>
component which renders children based on current route<Link>
component which dispatch navigation actions when clicked
<Route />
<Route>
components conditionally render when the current location matches their pattern.
The simplest version of a route renders the children when a url matches the pattern
import { Route } from '@touchtribe/redux-router'
...
<Route pattern={'/user/:id'}>
<p>This will render if the URL matches "/user/:id"
</Route>
pattern
can also be an instance of UrlPattern (createPattern
), which can be usefull for reusing patterns that are defined early in the process
import { Route, createPattern } from '@touchtribe/redux-router'
let Patterns = {
Home: createPattern('/'),
UserOverview: createPattern('/users/'),
UserDetail: createPattern('/users/:id')
}
...
<Route pattern={Patterns.Home}>
<p>This will render if the route matches "/user/:id"
</Route>
The <Route>
component also accepts a component
prop to render if it matches:
const UserDetailPage = (props) => <div>User Detail Page</div>
...
<Route pattern={'/user/:id'} component={UserDetailPage} />
For more control, children
also accepts a render-function
<Route pattern={'/user/:id'}>
{({location, pattern, match, ...props}) => (
match
? <p>this will render if the URL matches "/user/:id"
: <p>this will render otherwise</p>
)}
</Route>
The idea behind redux-router
doesn't allow for nested routes, since every route should come from a single source.
While <Route>
components can be nested, the patterns don't stack.
<Route pattern={'/users'}>
<p>This will be rendered if the URL matches "/users"</p>
<Route pattern={'/:id'}>
<p>This will be rendered if the URL matches "/:id"</p>
</Route>
<Route pattern={'/users/:id'}>
<p>This will be rendered if the URL matches "/users/:id"</p>
</Route>
</Route>
<Route>
makes basic component-per-page navigation easy:
<React.Fragment>
<Route pattern={'/'} component={HomePage} />
<Route pattern={'/users'} component={UserOverviewPage} />
<Route pattern={'/users/:id'} component={UserDetailPage} />
</React.Fragment>
<Link>
The link component is easy to use and renders as <a href="...">...</a>
by default
import { Link } from '@touchtribe/redux-router'
<Link href="/" className="blue-link">
Click me
</Link>
It can also be used together with a pattern to promote reusability
...
<Link href={Patterns.UserOverview}>
To user overview
</Link>
To change props when a link is active (matched), you can use the activeProps
prop
className
is a special case, in which className gets concatenated with activeProps.className
<Link href="/" className="link" activeProps={{className: 'link--active'}}>
To homepage
</Link>
To render a Link
as a different component, you can use the as
prop:
the Component needs to propogate onClick
for this to work
...
<Link href={Patterns.UserOverview} as={Button}>
To user overview
</Link>
To add parameters to the URL, the query
prop can be used. These will both be added as params for the Pattern as well as to the query-string
...
const UserOverviewPage = createPattern('/users/:userId')
<Link href={UserOverviewPage} query={{userId: 10}}>
To user detail
</Link>
To use replace
instead of push
, the replaceState
can be used:
...
<Link href={Patterns.UserOverview} replaceState>
To user overview
</Link>
Actions
@touchtribe/redux-router
providers the following action creators for navigation:
import { push, replace, go, back, forward, locationChanged, initializeLocation } from '@touchtribe/redux-router';
// to push a new route
push('/')
// to push a new route with a query
push('/', { userId: 10 })
// to push a new route using a pattern
const HomePage = createPattern('/')
push(HomePage)
// to push a new route using a pattern with params
const UserDetailPage = createPattern('/user/:userId')
push(UserDetailPage, { userId: 10 })
// replace and push use the same parameters
// to replace your current route
replace('/')
// Navigates forwards or backwards
go(3)
go(-2)
// equivalent of the browser back button
back()
// equivalent of the browser forward button
forward()
All action-types can be retrieved by using <action>.toString()
or String(<action>)
, e.g.: String(push)
.
This is necessary to listen for them in either your side-effects or your reducers:
const reducer = (state, action) => {
switch(action.type) {
case String(push):
...
case String(replace):
...
case String(initializeLocation):
...
case String(locationChanged):
...
}
return state
}
Helpers
createPattern(href)
To create patterns for reuse within the application
parseLocation(location, patterns)
Matches the current location against the given patterns and returns the information which would be reflected in the state:
{
location,
pattern,
matches,
params,
hash
}