├── .babelrc ├── .gitignore ├── README.md ├── package.json ├── src ├── containers │ ├── About.js │ ├── App.js │ ├── Home.js │ ├── Subpage.js │ └── index.js ├── index.js ├── reducers │ ├── app-reducer.js │ └── router-reducer.js └── redux-router-init.js ├── static └── images │ └── us.png └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "es2015", 4 | "react" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Example: React + Router + Redux + Immutable.js 2 | 3 | This is a barebones working [React](https://facebook.github.io/react/) application that demonstrates combining [React Router](https://github.com/reactjs/react-router) and [Redux](http://redux.js.org/) using an [Immutable.js](https://facebook.github.io/immutable-js/) store. We use [Babel](https://babeljs.io/) to transpile ES6 to ES5 that can be run in the browser and [Webpack](https://webpack.github.io/) to trigger Babel and pack the results. 4 | 5 | ## Motivation: 6 | 7 | * React Router to efficiently manage multiple areas of the app 8 | * Redux to keep a clean, reproducible state 9 | * Immutable.js to make manipulations of state easier and more disciplined 10 | 11 | ## Overview 12 | 13 | I started with a plain React application, then added React Router. I extended this with a Redux store for managing state. I used the [react-router-redux](https://github.com/reactjs/react-router-redux) package to help manage the interplay between these two packages. 14 | 15 | If you want your Redux store itself to be immutable, then you to modify things a little bit from the default Redux setup. The [redux-immutable](https://github.com/gajus/redux-immutable) package gets us most of the distance out-of-the-box, but there's [some more customization you to need to do](https://github.com/gajus/redux-immutable#using-with-react-router-redux) when combining Redux, Immutable.js, AND React Router. 16 | 17 | My goal was to provide a **working example** of combining all these packages and snippets together. 18 | 19 | **Note**: I'm using `hashHistory` intentionally since for my use case we are supporting older versions of Internet Explorer and don't want route changes to cause a reload when `browserHistory` is not supported. See [more info](https://github.com/reactjs/react-router/blob/master/docs/guides/Histories.md#hashhistory). 20 | 21 | ## Getting started 22 | 23 | Install required dependencies with 24 | 25 | npm install 26 | 27 | You can then run the webpack development server on http://localhost:3000/ with 28 | 29 | npm start 30 | 31 | You also can build the webpack results and save them to the `dist/` folder with 32 | 33 | npm build 34 | 35 | 36 | ## Resources 37 | 38 | Kudos and hat-tips to all the people involved in creating React, Redux, React Router, Immutable.js, Babel, Webpack, React-router-redux, redux-immutable and the authors of the following resources. 39 | 40 | * [redux-immutable] [Using react-immutable with react-router-redux](https://github.com/gajus/redux-immutable#using-with-react-router-redux) 41 | * [react-router-redux] [Brief overview of how to use react router and redux with an Immutable.js store](https://github.com/reactjs/react-router-redux#what-if-i-use-immutablejs-with-my-redux-store) 42 | * [react-router-redux] [Instructions on configuring react-router-redux with an Immutable.js store ](https://github.com/reactjs/react-router-redux/issues/301) 43 | The `createSelectLocationState` function defined here is crucial and is only found at this link and this other [issue](https://github.com/reactjs/react-router-redux/issues/314#issuecomment-190678756) 44 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-router-redux-immutable", 3 | "version": "1.0.0", 4 | "description": "An example React app using React Router, Redux and Immutable.js", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "webpack --progress --colors --config webpack.config.js && cp -r static/* dist/", 8 | "start": "webpack-dev-server --progress --colors --config webpack.config.js" 9 | }, 10 | "author": "", 11 | "license": "MIT", 12 | "dependencies": { 13 | "babel-core": "^6.9.1", 14 | "babel-loader": "^6.2.4", 15 | "babel-plugin-react-transform": "^2.0.2", 16 | "babel-preset-es2015": "^6.9.0", 17 | "babel-preset-react": "^6.5.0", 18 | "history": "^3.0.0", 19 | "html-webpack-plugin": "^2.21.0", 20 | "immutable": "^3.8.1", 21 | "react": "0.14.8", 22 | "react-dom": "0.14.8", 23 | "react-redux": "^4.4.5", 24 | "react-router": "^2.4.1", 25 | "react-router-redux": "^4.0.5", 26 | "redux": "^3.5.2", 27 | "redux-immutable": "^3.0.6", 28 | "webpack": "^1.13.1", 29 | "webpack-dev-server": "^1.14.1" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/containers/About.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default class extends React.Component { 4 | render () { 5 | return
6 |

About

7 |

Lorem ipsum dolor sit amet

8 |
; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/containers/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Link} from 'react-router' 3 | 4 | export default class extends React.Component { 5 | render () { 6 | return (
7 | 8 |

9 | App  10 | Home  11 | About  12 | Subpage 13 |

14 | 15 | {this.props.children} 16 |
); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/containers/Home.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Link} from 'react-router' 3 | 4 | export default class extends React.Component { 5 | render () { 6 | return
7 |

Homepage

8 |

United States (flag) A static image

9 |
; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/containers/Subpage.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default class extends React.Component { 4 | render () { 5 | return
6 |

Subpage

7 |

Lorem ipsum dolor sit amet

8 |
; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/containers/index.js: -------------------------------------------------------------------------------- 1 | export {default as App} from "./App"; 2 | export {default as Home} from "./Home"; 3 | export {default as About} from "./About"; 4 | export {default as Subpage} from "./Subpage"; 5 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /* External dependencies */ 2 | import React from 'react'; 3 | import { render } from 'react-dom'; 4 | import { Provider } from 'react-redux' 5 | import { Router, Route, IndexRoute } from 'react-router' 6 | 7 | /* Internal dependencies */ 8 | import {store, history} from './redux-router-init.js' 9 | import {App, Home, About, Subpage} from './containers' 10 | 11 | ////////////////////////////////////////////////// 12 | 13 | const root = document.createElement('div') 14 | document.body.appendChild(root) 15 | 16 | // A router with enhanced history nested in an immutable, location-aware 17 | // redux state provider 18 | render( 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | , 28 | root 29 | ) 30 | -------------------------------------------------------------------------------- /src/reducers/app-reducer.js: -------------------------------------------------------------------------------- 1 | import Immutable from 'immutable'; 2 | 3 | const initialState = Immutable.fromJS({}); 4 | 5 | export default (state = initialState, action) => { 6 | // Do something here ... 7 | 8 | return state; 9 | }; 10 | -------------------------------------------------------------------------------- /src/reducers/router-reducer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A custom router reducer to support an Immutable store. 3 | * See: https://github.com/gajus/redux-immutable#using-with-react-router-redux 4 | */ 5 | import Immutable from 'immutable'; 6 | import { 7 | LOCATION_CHANGE 8 | } from 'react-router-redux'; 9 | 10 | const initialState = Immutable.fromJS({ 11 | locationBeforeTransitions: null 12 | }); 13 | 14 | export default (state = initialState, action) => { 15 | if (action.type === LOCATION_CHANGE) { 16 | return state.merge({ 17 | locationBeforeTransitions: action.payload 18 | }); 19 | } 20 | 21 | return state; 22 | }; 23 | -------------------------------------------------------------------------------- /src/redux-router-init.js: -------------------------------------------------------------------------------- 1 | /* External dependencies */ 2 | import { combineReducers } from 'redux-immutable'; 3 | import Immutable from 'immutable'; 4 | import { createStore } from 'redux'; 5 | import { hashHistory } from 'react-router'; 6 | import { syncHistoryWithStore } from 'react-router-redux' 7 | 8 | /* Internal dependencies */ 9 | import appReducer from './reducers/app-reducer' 10 | import routerReducer from './reducers/router-reducer' 11 | 12 | //////////////////////////////////////////////// 13 | 14 | /** 15 | * Combine reducers into root reducer and create store. 16 | * Note thate 'combineReducers' is a redux-immutable version 17 | */ 18 | const rootReducer = combineReducers({ 19 | app: appReducer, 20 | routing: routerReducer 21 | }) 22 | const initialState = Immutable.Map(); 23 | 24 | const store = createStore(rootReducer, initialState, 25 | // Enable redux dev tools 26 | window.devToolsExtension && window.devToolsExtension() 27 | ); 28 | 29 | /* Create enhanced history object for router */ 30 | const createSelectLocationState = () => { 31 | let prevRoutingState, prevRoutingStateJS; 32 | return (state) => { 33 | const routingState = state.get('routing'); // or state.routing 34 | if (typeof prevRoutingState === 'undefined' || prevRoutingState !== routingState) { 35 | prevRoutingState = routingState; 36 | prevRoutingStateJS = routingState.toJS(); 37 | } 38 | return prevRoutingStateJS; 39 | }; 40 | }; 41 | 42 | const history = syncHistoryWithStore(hashHistory, store, { 43 | selectLocationState: createSelectLocationState() 44 | }); 45 | 46 | 47 | //////////////////////////////////////////////// 48 | 49 | /* Exports */ 50 | export { store, history } 51 | -------------------------------------------------------------------------------- /static/images/us.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sjparsons/react-router-redux-immutable/8ef8eeacbec7132393ae86275247d48765ebeed8/static/images/us.png -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | 4 | const PORT = 3000; 5 | 6 | module.exports = { 7 | devtool: '#inline-source-map', 8 | 9 | entry: [ 10 | './src/index' 11 | ], 12 | module: { 13 | loaders: [ 14 | { 15 | test: /\.(js|jsx)?$/, 16 | exclude: [/node_modules/], 17 | loader: 'babel' 18 | } 19 | ] 20 | }, 21 | resolve: { 22 | extensions: ['', '.js', '.jsx'] 23 | }, 24 | output: { 25 | path: __dirname + '/dist', 26 | publicPath: '/', 27 | filename: 'bundle.js' 28 | }, 29 | devServer: { 30 | contentBase: './static', 31 | port: PORT 32 | }, 33 | plugins: [ 34 | new HtmlWebpackPlugin({ 35 | title: "Example: React Router Redux Immutable" 36 | }) 37 | ] 38 | }; 39 | --------------------------------------------------------------------------------