Skip to content

Instantly share code, notes, and snippets.

@faceyspacey
Last active June 16, 2017 06:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save faceyspacey/43ce30a8226cbfe5c74c51fa0b87d419 to your computer and use it in GitHub Desktop.
Save faceyspacey/43ce30a8226cbfe5c74c51fa0b87d419 to your computer and use it in GitHub Desktop.
code for redux-first router pre release article
routesMap: {
HOME: '/',
POST: '/feed/post/:id',
USER: '/users/:slug',
}
which I'm sure you're familiar with. But then what you can do is this:
store.dispatch({ type: 'HOME' })
store.dispatch({ type: 'POST', payload: { id: 123 } })
store.dispatch({ type: 'USER', payload: { slug: 'james-gillmore' } })
const slug = getSlugFromSomewhere()
<Link href='/' />
<Link href={{ type: 'POST', payload: { id: 123 } }} />
<Link href={['user', $slug]} />
// ;) sneak-peak from React Universal Component + Webpack Flush Chunks:
<Link prefetch href={{ type: 'POST', payload: { id: 123 } }} />
const userSlugReducer = (state, action) =>
action.type === 'USER' ? action.payload.slug : state
const usersCacheReducer = (state, action) =>
action.type === 'USERS_LOADED' ? action.payload.users : state
const UserComponent = ({ user }) =>
<div>{user.name}</div>
// in a real app you'd obviously use reselect for memoization
const mapStateToProps = ({ userSlugReducer: slug, usersCacheReducer: users }) => ({
user: users[slug]
})
export default connect(mapStateToProps)(UserComponent)
Here's another kind of reducer you'd likely have:
const pageReducer = (state, action) => {
switch(action.type) {
case 'HOME':
return 'homeScene'
case 'POST':
return 'postScene'
case 'USER':
return 'userScene'
}
return state
}
const routesMap: {
HOME: '/',
POST: '/feed/post/:id',
USER: { path: '/users/:slug', chunks: [import('User')] },
}
const { reducer, middleware, enhancer } = routerForBrowser()
const reducers = combineReducers({ router: reducer, yourReducer })
const enhancers = compose(enhancer, applyMiddleware(middleware)
const store = createStore(reducers, enhancers)
<Provider store={store}>
<RouterProvider store={store}>
<Root />
</RouterProvider>
</Provider>
ReactDOM.render(
<Provider store={store}>
<RouterProvider store={store}>
<YourAppComponent />
</RouterProvider>
</Provider>,
document.getElementById('root')
)
<Fragment forRoute='/home/bio/:user'>
<h1>This is the bio page!</j1>
<ConnectedUser />
</Fragment>
Pre Release: Redux-First Router — A Step Beyond Redux-Little-Router
The purpose of this article is to present a solution to a problem. Specifically to debunk the effectiveness of route-matching components + nested routes when using Redux, while discovering a better, simpler, obvious way.
As a conscious developer, we have realize our #1 goal is in fact staying conscious. That specifically means staying alert to your blind spots. 
Our blind spots are the traps that lead us to taking longer less intuitive development paths when there are obvious better/faster ways perfectly within our capabilities that we aren’t seeing.
As a result we are always looking for a faster more intuitive way to do things.
It’s no surprise then every few months/years/days we find ourselves confronted by the fact that there’s a better way to do what we’re doing.
Sometimes these evolutions are so profound that it leads to the entire development community switching how they do things, as is the case both with React and Redux.
Today what I’m going to propose is the same thing with Routing. Yes, you heard me. The less sexy, supposedly solved problem that we’ve all been reinventing for years, from language to language, framework to framework. Or, just following the status quo, and paying the price for it.
REDUX-LITTLE-ROUTER
We’re going to start our journey through understanding the problem by looking at the best thing out when it comes to routing within Redux: redux-little-router.
If you aren’t using Redux, React Router is still the b̶e̶s̶t̶ recommended solution. So, this article is strictly for Redux developers. 
How does redux-little-router work?
Taking an example from its readme, you start out by defining and nesting routes like this:
const routes = {
'/users': {
title: 'Users'
},
'/users/:slug': {
title: 'User profile for:'
},
// nested route:
'/home': {
title: 'Home',
'/repos': {
title: 'Repos',
'/:slug': {
title: 'Repo about:'
}
}
}
}
Then you do what’s typical of redux routers and compose custom middleware, reducer, and enhancers:
const { reducer, middleware, enhancer } = routerForBrowser()
const reducers = combineReducers({ router: reducer, ...others })
const enhancers = compose(enhancer, applyMiddleware(middleware)
const store = createStore(reducers, enhancers)
Lastly, you setup your provider(s):
import { Provider } from 'react-redux'
import { RouterProvider } from 'redux-little-router'
import configureStore from './configureStore'
const store = configureStore()()
ReactDOM.render(
<Provider store={store}>
<RouterProvider store={store}>
<YourAppComponent />
</RouterProvider>
</Provider>,
document.getElementById('root')
)
The primary ways to use it are:
Change the URL while dispatching a corresponding action:
push('/users/james-gillmore?foo=bar')
Conditionally render something using their “route-matching” component <Fragment /> (same thing as <Route /> in React Router):
<Fragment forRoute='/home/repos/:slug'>
<h1>This a code repo!</j1>
<ConnectedRepo />
</Fragment>
Create a real link for SEO:
<Link href='/home/repos/react-universal-component'>
Go To React Universal Component
</Link>
You can also nest fragments/routes like in React Router. Here’s an example from their readme:
<Fragment forRoute='/home'>
<div>
<h1>Home</h1>
<Fragment forRoute='/bio'>
<div>
<h2>Bios</h2>
<Fragment forRoute='/dat-boi'>
<div>
<h3>Dat Boi</h3>
<p>Something something whaddup</p>
</div>
</Fragment>
</div>
</Fragment>
</div>
</Fragment>
One thing to note about this example is there is a page called '/home/bio' that will show just<h2>Bios</h2> . 
So obviously the goal of that snippet is to show a page for the URL '/home/bio/dat-boi'. Similar to what you can do with React Router. 
Before we continue it’s important to look at the actions dispatched that allow for this:
{
pathname: '/home/repos/react-universl-component',
route: '/home/repos/:slug',
params: {
slug: 'react-universal-component'
},
query: {
foo: 'bar'
},
search: '?foo=bar',
result: {
title: 'Repos about:'
parent: {
title: 'Repos',
parent: {
title: 'Home'
}
}
}
}
{
pathname: '/home/repos/react-universl-component',
route: '/home/repos/:slug',
params: {
slug: 'react-universal-component'
},
query: {
foo: 'bar'
},
search: '?foo=bar',
result: {
title: 'Repos about:'
parent: {
title: 'Repos',
parent: {
title: 'Home'
}
}
}
previous: {
pathname: '/home/repos',
route: '/home/repos',
params: {},
query: {},
result: {
title: 'Repos about:'
parent: {
title: 'Repos',
}
}
}
}
const routesMap = {
TYPE_A: '/home/repos/:slug'
TYPE_B: '/users/:slug,
TYPE_C: '/posts/:id',
HOME: '/'
}
dispatch({ type: 'REPO', payload: { slug: 'react-universal-component' } })
dispatch({ type: 'USER', payload: { slug: 'james-gillmore' } })
dispatch({ type: 'POST', payload: { id: 123 } })
dispatch({ type: 'HOME' })
const userSlug = (state, action) =>
action.type === 'USER' ? action.payload.slug : state
// not all types need to have URLs:
const usersCache = (state, action) =>
action.type === 'USERS_LOADED' ? action.payload.users : state
const pageReducer = (state, action) => {
switch(action.type) {
case 'HOME':
return 'homeScene'
case 'POST':
return 'postScene'
case 'REPOS':
return 'reposScene'
case 'REPO':
return 'repoScene'
case 'USER':
return 'userScene'
}
return state
}
const PageComponent = ({ page }) => {
const Page = pages[page]
return Page <Page /> || null
}
const mapState = ({ page }) => ({ page })
export default connect(mapState)(UserComponent)
const pages = {
homeScene: HomeComponent,
postScene: PostComponent,
reposScene: ReposComponent,
repoScene: RepoComponent,
userScene: UserComponent
}
{
type: 'LOCATION_CHANGED',
pathname: '/home/repos/react-universl-component',
route: '/home/repos/:slug',
params: {
slug: 'react-universal-component'
},
query: {
foo: 'bar'
},
search: '?foo=bar',
result: {
title: 'Repos about:'
parent: {
title: 'Repos',
parent: {
title: 'Home'
}
}
}
}
const userThunk = async (dispatch, getState) => {
const { slug } = getState().location.payload
const data = await fetch(`/api/user/${slug}`)
const user = await data.json()
const action = { type: 'USER_FOUND', payload: { user } }
dispatch(action)
}
const routesMap = {
USER: { path: '/user/:slug', thunk: userThunk },
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment