Skip to content

Instantly share code, notes, and snippets.

@shinzui
Forked from HenrikJoreteg/README.md
Last active Aug 29, 2015
Embed
What would you like to do?
Minimalist routing in Redux

Why would you want to do this? Because you often don't need more. It's nice to not have to think about your "router" as this big special thing.

Instead, with this approch, your app's current pathname is just another piece of state, just like anything else.

This also means that when doing server-side rendering of a redux app, you can just do:

var app = require('your/redux/app')
var React = require('react')

// pass the URL like any other piece of data
React.renderToString(app.render({url: '/about'}))

The react-internal-nav component makes sure internal navigation clicks are captured, then we turn it into an SET_URL action dispatch.

This example has extremely simple url matching with just a few if statements, but this could easily be elaborated on if you needed it, using some kind of lightweight string route matcher, such as:

While standalone history management tools exist, such as https://browserstate.github.io/history.js/demo/ or https://github.com/rackt/history they're concerned with proper handling the state part of pushState APIs. Personally, I don't want to put state there anyway and if you don't care about that part, the actual browser support for plain pushState is quite good.

import { SET_URL } from '../constants/ActionTypes'
function pushState(url) {
if (url !== window.location.pathname) {
window.history.pushState({}, '', url)
}
}
export function setUrl(text) {
pushState(text)
return {
type: SET_URL,
text
}
}
export const SET_URL = 'SET_URL'
/* global __IS_DEV__*/
import React from 'react'
import { createStore, combineReducers } from 'redux'
import { Provider } from 'react-redux'
import FooApp from './FooApp'
import * as reducers from '../reducers'
import * as UrlActions from '../actions/UrlActions'
export default class App {
// note that we take data as an argument here
render (data) {
// ...and pass it through as initial state for server rendering
let store = createStore(createStore(reducers), data)
if (typeof window !== 'undefined') {
window.addEventListener('popstate', () => {
store.dispatch(UrlActions.setUrl(window.location.pathname))
})
store.dispatch(UrlActions.setUrl(window.location.pathname))
}
return (
<Provider store={store}>
{() => <FooApp />}
</Provider>
)
}
}
import React from 'react'
import { bindActionCreators } from 'redux'
import { Connector } from 'react-redux'
import InternalNav from 'react-internal-nav'
import AboutPage from '../components/about-page'
import DataPage from '../components/data-page'
import PersonDetail from '../components/person-detail'
import * as UrlActions from '../actions/UrlActions'
export default class FooApp {
render() {
return (
<Connector>
{this.renderChild}
</Connector>
)
}
renderChild({ personData: {persons}, dispatch, url }) {
const actions = {
...bindActionCreators(UrlActions, dispatch)
}
let page
// more sophisticated url matching could easily be done with any of these:
// https://github.com/Matt-Esch/http-hash
// https://github.com/glassresistor/i40
// https://github.com/bevacqua/ruta3
if (url === '/') {
page = (<h1>Home</h1>)
} else if (url === '/about') {
page = (<AboutPage/>)
} else if (url === '/data') {
page = (<DataPage persons={persons}/>)
} else {
let match
persons.some(person => {
if (person.slug === url.slice(1)) {
match = person
return true
}
})
if (match) {
page = (<PersonDetail person={match} {...actions}/>)
} else {
page = (<h1>404</h1>)
}
}
return (
<InternalNav onInternalNav={actions.setUrl}>
<header>
<nav role='navigation'>
<a href='/'>Example.com</a>
<span>
<a href="/data">Notes</a>
<a href="/about">About</a>
</span>
</nav>
</header>
{page}
</InternalNav>
)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment