├── .gitignore
├── .babelrc
├── static
└── images
│ └── us.png
├── src
├── containers
│ ├── index.js
│ ├── About.js
│ ├── Subpage.js
│ ├── Home.js
│ └── App.js
├── reducers
│ ├── app-reducer.js
│ └── router-reducer.js
├── index.js
└── redux-router-init.js
├── webpack.config.js
├── package.json
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "es2015",
4 | "react"
5 | ]
6 | }
7 |
--------------------------------------------------------------------------------
/static/images/us.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sjparsons/react-router-redux-immutable/HEAD/static/images/us.png
--------------------------------------------------------------------------------
/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/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/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/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/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 |
A static image
9 |
;
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------