Think of your app in terms of states, not routes or components. Connect your components and just dispatch Flux Standard Actions!
During the development of Rudy, a few versions were released under different names and npm tags. All of these plus several PRs have been combined to a stable, up-to-date and mostly compatible version which will be supported long-term. (See the migration instructions for version 2.)
Feature development efforts and the next version Rudy will be moved to a different repo in our Respond framework organization.
To be able to use Redux as is while keeping the address bar in sync. To define paths as actions, and handle path params and query strings as action payloads.
The address bar and Redux actions should be bi-directionally mapped, including via the browser's back/forward buttons. Dispatch an action and the address bar updates. Change the address, and an action is dispatched.
In addition, here are some obstacles Redux-First Router seeks to avoid:
- Rendering from state that doesn't come from Redux
- Dealing with the added complexity from having state outside of Redux
- Cluttering components with route-related code
- Large API surface areas of frameworks like
react-router
andnext.js
- Routing frameworks getting in the way of optimizing animations (such as when animations coincide with component updates).
- Having to do route changes differently in order to support server-side rendering.
yarn add redux-first-router
A minimal <Link>
component exists in the separate package redux-first-router-link
.
// configureStore.js
import { applyMiddleware, combineReducers, compose, createStore } from 'redux'
import { connectRoutes } from 'redux-first-router'
import page from './pageReducer'
const routesMap = {
HOME: '/',
USER: '/user/:id'
}
export default function configureStore(preloadedState) {
const { reducer, middleware, enhancer, thunk } = connectRoutes(routesMap)
const rootReducer = combineReducers({ page, location: reducer })
const middlewares = applyMiddleware(middleware)
const enhancers = compose(enhancer, middlewares)
const store = createStore(rootReducer, preloadedState, enhancers)
return { store, thunk }
}
// pageReducer.js
import { NOT_FOUND } from 'redux-first-router'
const components = {
HOME: 'Home',
USER: 'User',
[NOT_FOUND]: 'NotFound'
}
export default (state = 'HOME', action = {}) => components[action.type] || state
// App.js
import React from 'react'
import { connect } from 'react-redux'
// Contains 'Home', 'User' and 'NotFound'
import * as components from './components';
const App = ({ page }) => {
const Component = components[page]
return <Component />
}
const mapStateToProps = ({ page }) => ({ page })
export default connect(mapStateToProps)(App)
// components.js
import React from 'react'
import { connect } from 'react-redux'
const Home = () => <h3>Home</h3>
const User = ({ userId }) => <h3>{`User ${userId}`}</h3>
const mapStateToProps = ({ location }) => ({
userId: location.payload.id
})
const ConnectedUser = connect(mapStateToProps)(User)
const NotFound = () => <h3>404</h3>
export { Home, ConnectedUser as User, NotFound }
- Automatically change Page
<title>
- Embed SEO-friendly links
- Style active links
- Dispatch thunks on route changes
- Perform redirects
- Use hash-based routes/history
- Restore scroll position
- Handle query strings
- Pre- & post-process route changes
- Enable code-splitting
- Use Redux Devtools to debug route changes
// reducers/title.js
const DEFAULT = 'RFR demo'
export default (state = DEFAULT, action = {}) => {
switch (action.type) {
case 'HOME':
return DEFAULT
case 'USER':
return `${DEFAULT} - user ${action.payload.id}`
default:
return state
}
}
// reducers/index.js
export { default as title } from './title'
// configureStore.js
+ import * as reducers from './reducers';
- const rootReducer = combineReducers({ page, title, location: reducer })
+ const rootReducer = combineReducers({ ...reducers, page, title, location: reducer })
yarn install redux-first-router-link
import Link from 'redux-first-router-link'
<Link to="/user/123">User 123</Link>
// Recommended approach - URLs can be changed by only modifying routesMap.js
<Link to={{ type: 'USER', payload: { id: 456 } }}>User 456</Link>
// Same as above but without SEO benefits
const mapDispatchToProps = dispatch => ({
onClick: id => dispatch({ type: 'USER', payload: { id } })
})
The link components are in a separate package, see above. <NavLink />
contains props such as activeStyle
and activeClassName
.
TODO
TODO
Redirect to another route based on payload
and state
.
TODO
TODO: Introduce options.js and createHashHistory
from history
/rudy-history
.
See the migration instructions.
TODO
TODO
TODO
TODO
// configureStore.js
- const enhancers = compose(enhancer, middlewares)
+ const composeEnhancers =
+ typeof window === 'object' && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
+ ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({})
+ : compose
+ const enhancers = composeEnhancers(enhancer, middlewares)
Dispatch { type: 'USER', payload: { id: 13 } }
and see the route change.
Then use the Inspector to time travel back to HOME
.
TODO: Usage together with
react-universal-component
, babel-plugin-universal-import
, webpack-flush-chunks
.
We use commitizen, run npm run cm
to make commits. A command-line form will appear, requiring you answer a few questions to automatically produce a nicely formatted commit. Releases, semantic version numbers, tags, changelogs and publishing will automatically be handled based on these commits thanks to [semantic-release](https:/
/github.com/semantic-release/semantic-release).
-
react-universal-component. Made to work perfectly with Redux-First Router.
-
webpack-flush-chunks. The foundation of our
Universal
product line.