├── .babelrc ├── .gitignore ├── README.md ├── actions ├── api.js ├── error.js └── index.js ├── components ├── .keep └── MessageModal.js ├── containers ├── App.js ├── DevTools.js ├── Root.dev.js ├── Root.js └── Root.prod.js ├── index.html ├── index.js ├── middleware ├── .keep └── index.js ├── package.json ├── reducers └── index.js ├── routes.js ├── server.js ├── store ├── configureStore.dev.js ├── configureStore.js └── configureStore.prod.js └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "stage": 0, 3 | "env": { 4 | "development": { 5 | "plugins": [ 6 | "react-transform" 7 | ], 8 | "extra": { 9 | "react-transform": { 10 | "transforms": [{ 11 | "transform": "react-transform-hmr", 12 | "imports": ["react"], 13 | "locals": ["module"] 14 | }] 15 | } 16 | } 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sample_redux 2 | 3 | ## init 4 | 5 | ``` 6 | npm init 7 | ``` 8 | 9 | ## server 10 | 11 | ``` 12 | npm start 13 | ``` 14 | 15 | open [http://localhost:3000](http://localhost:3000) 16 | -------------------------------------------------------------------------------- /actions/api.js: -------------------------------------------------------------------------------- 1 | export function createApiMeta(url) { 2 | return () => ({ 3 | api: { 4 | url: url 5 | } 6 | }) 7 | } 8 | -------------------------------------------------------------------------------- /actions/error.js: -------------------------------------------------------------------------------- 1 | export function createErrorMeta(message) { 2 | return { 3 | error: { 4 | message: message 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /actions/index.js: -------------------------------------------------------------------------------- 1 | import { createAction } from 'redux-actions' 2 | 3 | import { createErrorMeta } from './error' 4 | 5 | function empty() { 6 | return {} 7 | } 8 | 9 | export const RESET_ERROR_MESSAGE = 'RESET_ERROR_MESSAGE'; 10 | 11 | export let resetErrorMessage = createAction(RESET_ERROR_MESSAGE); 12 | 13 | export const SHOW_ERROR_MESSAGE = 'SHOW_ERROR_MESSAGE'; 14 | 15 | export let showErrorMessage = createAction(SHOW_ERROR_MESSAGE, empty, createErrorMeta); 16 | 17 | export function showErrorMessageDelayed(message, delay = 1000) { 18 | return dispatch => { 19 | setTimeout(() => { 20 | dispatch(showErrorMessage(message)); 21 | }, delay); 22 | }; 23 | } 24 | 25 | 26 | export const SHOW_MODAL = "SHOW_MODAL"; 27 | export const HIDE_MODAL = "HIDE_MODAL"; 28 | 29 | export let showModal = createAction(SHOW_MODAL, (title, message) => ({title: title, message: message})); 30 | export let hideModal = createAction(HIDE_MODAL); 31 | 32 | 33 | import { createApiMeta } from './api' 34 | 35 | export const SERVICE_GET_DATA = "SERVICE_GET_DATA"; 36 | 37 | export let serviceGetData = createAction(SERVICE_GET_DATA, empty(), 38 | createApiMeta("http://example.com")); 39 | -------------------------------------------------------------------------------- /components/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yasuhiro-okada-aktsk/sample_redux/83153746bd8a41705a6115017404612be38881ef/components/.keep -------------------------------------------------------------------------------- /components/MessageModal.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | 3 | class SampleModal extends Component { 4 | constructor(props) { 5 | super(props); 6 | } 7 | 8 | componentDidMount() { 9 | this.showModal(); 10 | } 11 | 12 | componentDidUpdate(prevProps, prevState) { 13 | this.showModal(); 14 | } 15 | 16 | showModal() { 17 | $('#messageModal').modal(this.props.modal.visibility) 18 | } 19 | 20 | render() { 21 | const { title, message } = this.props.modal; 22 | 23 | return ( 24 | 39 | ); 40 | } 41 | } 42 | 43 | SampleModal.propTypes = { 44 | modal: PropTypes.object.isRequired, 45 | onPrimaryButton: PropTypes.func.isRequired 46 | }; 47 | 48 | export default SampleModal; 49 | -------------------------------------------------------------------------------- /containers/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import { bindActionCreators } from 'redux'; 3 | import { connect } from 'react-redux'; 4 | import { pushState } from 'redux-router'; 5 | import * as Actions from '../actions'; 6 | 7 | import MessageModal from "../components/MessageModal.js" 8 | 9 | class App extends Component { 10 | constructor(props) { 11 | super(props); 12 | } 13 | 14 | handleDismissClick(e) { 15 | this.props.resetErrorMessage(); 16 | e.preventDefault(); 17 | } 18 | 19 | handleShowError(e) { 20 | this.props.showErrorMessage("sample error!!"); 21 | e.preventDefault(); 22 | } 23 | 24 | handleShowErrorDelayed(e) { 25 | this.props.showErrorMessageDelayed("delayed sample error!!"); 26 | e.preventDefault(); 27 | } 28 | 29 | handleShowModal(e) { 30 | const title = "Sample Modal"; 31 | const message = `Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut 32 | labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut 33 | aliquip ex ea commodo consequat. 34 | Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. 35 | Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.`; 36 | this.props.showModal(title, message); 37 | e.preventDefault(); 38 | } 39 | 40 | handleModalPrimaryButton(e) { 41 | this.props.hideModal(); 42 | e.preventDefault(); 43 | } 44 | 45 | handleFetch(e) { 46 | this.props.serviceGetData(); 47 | e.preventDefault(); 48 | } 49 | 50 | renderErrorMessage() { 51 | const { errorMessage } = this.props; 52 | if (!errorMessage) { 53 | return null; 54 | } 55 | 56 | return ( 57 |

58 | {errorMessage} 59 | {' '} 60 | ( 62 | Dismiss 63 | ) 64 |

65 | ); 66 | } 67 | 68 | renderSample() { 69 | return ( 70 | 74 | ); 75 | } 76 | 77 | renderModal() { 78 | const { messageModal } = this.props; 79 | 80 | return ( 81 |
82 | 85 | 86 | 87 |
88 | 89 | ); 90 | } 91 | 92 | renderFetch() { 93 | const { aService } = this.props; 94 | 95 | return ( 96 |
97 | 100 | 101 |
data : {aService.data}
102 |
{aService.isFetching ? "fetching..." : ("")}
103 |
104 | ); 105 | } 106 | 107 | render() { 108 | const { children, inputValue } = this.props; 109 | return ( 110 |
111 | {this.renderErrorMessage()} 112 |
113 | {this.renderSample()} 114 |
115 | {this.renderModal()} 116 |
117 | {this.renderFetch()} 118 | 119 | {children} 120 |
121 | ); 122 | } 123 | } 124 | 125 | App.propTypes = { 126 | // Injected by React Redux 127 | errorMessage: PropTypes.string, 128 | pushState: PropTypes.func.isRequired, 129 | inputValue: PropTypes.string.isRequired, 130 | messageModal: PropTypes.object.isRequired, 131 | // Injected by React Router 132 | children: PropTypes.node 133 | }; 134 | 135 | function mapStateToProps(state) { 136 | return { 137 | messageModal: state.messageModal, 138 | aService: state.aService, 139 | errorMessage: state.errorMessage, 140 | inputValue: state.router ? state.router.location.pathname.substring(1) : "" 141 | }; 142 | } 143 | 144 | function mapDispatchToProps(dispatch) { 145 | return { 146 | ...bindActionCreators(Actions, dispatch), 147 | pushState 148 | }; 149 | } 150 | 151 | export default connect(mapStateToProps, mapDispatchToProps)(App); 152 | -------------------------------------------------------------------------------- /containers/DevTools.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { createDevTools } from 'redux-devtools'; 3 | import LogMonitor from 'redux-devtools-log-monitor'; 4 | import DockMonitor from 'redux-devtools-dock-monitor'; 5 | 6 | export default createDevTools( 7 | 9 | 10 | 11 | ); 12 | -------------------------------------------------------------------------------- /containers/Root.dev.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import { Provider } from 'react-redux'; 3 | import { ReduxRouter } from 'redux-router'; 4 | import DevTools from './DevTools'; 5 | 6 | export default class Root extends Component { 7 | render() { 8 | const { store } = this.props; 9 | return ( 10 | 11 |
12 | 13 | 14 |
15 |
16 | ); 17 | } 18 | } 19 | 20 | Root.propTypes = { 21 | store: PropTypes.object.isRequired 22 | }; 23 | -------------------------------------------------------------------------------- /containers/Root.js: -------------------------------------------------------------------------------- 1 | if (process.env.NODE_ENV === 'production') { 2 | module.exports = require('./Root.prod'); 3 | } else { 4 | module.exports = require('./Root.dev'); 5 | } 6 | -------------------------------------------------------------------------------- /containers/Root.prod.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import { Provider } from 'react-redux'; 3 | import { ReduxRouter } from 'redux-router'; 4 | 5 | export default class Root extends Component { 6 | render() { 7 | const { store } = this.props; 8 | return ( 9 | 10 | 11 | 12 | ); 13 | } 14 | } 15 | 16 | Root.propTypes = { 17 | store: PropTypes.object.isRequired 18 | }; 19 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Redux Sample 5 | 6 | 7 | 8 | 9 | 10 |
11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import 'babel-core/polyfill'; 2 | import React from 'react'; 3 | import { render } from 'react-dom'; 4 | import Root from './containers/Root'; 5 | import configureStore from './store/configureStore'; 6 | 7 | const store = configureStore(); 8 | 9 | render( 10 | , 11 | document.getElementById('root') 12 | ); 13 | -------------------------------------------------------------------------------- /middleware/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yasuhiro-okada-aktsk/sample_redux/83153746bd8a41705a6115017404612be38881ef/middleware/.keep -------------------------------------------------------------------------------- /middleware/index.js: -------------------------------------------------------------------------------- 1 | export const logger = store => next => action => { 2 | console.log("before: %O", store.getState()); 3 | next(action); 4 | console.log("after: %O", store.getState()); 5 | }; 6 | 7 | export const api = store => next => action => { 8 | next(action); 9 | 10 | const { meta } = action; 11 | if (meta && meta.api) { 12 | setTimeout(() => { 13 | next( 14 | Object.assign(action, { 15 | payload: store.getState().aService.data + store.getState().aService.data.length, 16 | meta: { 17 | api: undefined 18 | } 19 | } 20 | )) 21 | }, 3000); 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sample_redux", 3 | "version": "0.0.0", 4 | "description": "sample_redux", 5 | "scripts": { 6 | "start": "node server.js" 7 | }, 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/yasuhiro-okada-aktsk/sample_redux.git" 11 | }, 12 | "license": "MIT", 13 | "dependencies": { 14 | "history": "^1.9.0", 15 | "humps": "^0.6.0", 16 | "isomorphic-fetch": "^2.1.1", 17 | "lodash": "^3.10.1", 18 | "normalizr": "^1.0.0", 19 | "react": "^0.14.0", 20 | "react-dom": "^0.14.0", 21 | "react-redux": "^2.1.2", 22 | "react-router": "^1.0.0-rc1", 23 | "redux": "^3.0.0", 24 | "redux-actions": "^0.8.0", 25 | "redux-logger": "^2.0.2", 26 | "redux-router": "^1.0.0-beta3", 27 | "redux-thunk": "^0.1.0" 28 | }, 29 | "devDependencies": { 30 | "babel-core": "^5.6.18", 31 | "babel-loader": "^5.1.4", 32 | "babel-plugin-react-transform": "^1.1.0", 33 | "concurrently": "^0.1.1", 34 | "express": "^4.13.3", 35 | "react-transform-hmr": "^1.0.0", 36 | "redux-devtools": "^3.0.0-beta-3", 37 | "redux-devtools-dock-monitor": "^1.0.0-beta-3", 38 | "redux-devtools-log-monitor": "^1.0.0-beta-3", 39 | "webpack": "^1.9.11", 40 | "webpack-dev-middleware": "^1.2.0", 41 | "webpack-hot-middleware": "^2.2.0" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /reducers/index.js: -------------------------------------------------------------------------------- 1 | import * as ActionTypes from '../actions'; 2 | import merge from 'lodash/object/merge'; 3 | import { routerStateReducer as router } from 'redux-router'; 4 | import { combineReducers } from 'redux'; 5 | import { handleActions } from 'redux-actions' 6 | 7 | // Updates error message to notify about the failed fetches. 8 | function errorMessage(state = null, action) { 9 | const { type, meta } = action; 10 | 11 | if (type === ActionTypes.RESET_ERROR_MESSAGE) { 12 | return null; 13 | } 14 | 15 | if (meta && meta.error && meta.error.message) { 16 | return meta.error.message; 17 | } 18 | 19 | return state; 20 | } 21 | 22 | const messageModalInitial = { 23 | visibility: "hide" 24 | }; 25 | 26 | const messageModal = handleActions({ 27 | SHOW_MODAL: (state, action) => ({ 28 | visibility: "show", 29 | title: action.payload.title, 30 | message: action.payload.message 31 | }), 32 | 33 | HIDE_MODAL: (state, action) => ({ 34 | visibility: "hide" 35 | }) 36 | }, messageModalInitial); 37 | 38 | const aServiceInitial = { 39 | isFetching: false, 40 | data: "" 41 | }; 42 | 43 | const aService = handleActions({ 44 | SERVICE_GET_DATA: (state, action) => ({ 45 | isFetching: action.meta.api, 46 | data: action.payload ? action.payload : state.data 47 | }) 48 | }, aServiceInitial); 49 | 50 | const rootReducer = combineReducers({ 51 | messageModal, 52 | aService, 53 | errorMessage, 54 | router 55 | }); 56 | 57 | export default rootReducer; 58 | -------------------------------------------------------------------------------- /routes.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Route } from 'react-router'; 3 | import App from './containers/App'; 4 | 5 | export default ( 6 | 7 | 8 | ); 9 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var webpackDevMiddleware = require('webpack-dev-middleware'); 3 | var webpackHotMiddleware = require('webpack-hot-middleware'); 4 | var config = require('./webpack.config'); 5 | 6 | var app = new require('express')(); 7 | var port = 3000; 8 | 9 | var compiler = webpack(config); 10 | app.use(webpackDevMiddleware(compiler, { noInfo: true, publicPath: config.output.publicPath })); 11 | app.use(webpackHotMiddleware(compiler)); 12 | 13 | app.use(function(req, res) { 14 | res.sendFile(__dirname + '/index.html'); 15 | }); 16 | 17 | app.listen(port, function(error) { 18 | if (error) { 19 | console.error(error); 20 | } else { 21 | console.info("==> 🌎 Listening on port %s. Open up http://localhost:%s/ in your browser.", port, port); 22 | } 23 | }); 24 | -------------------------------------------------------------------------------- /store/configureStore.dev.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware, compose } from 'redux'; 2 | import { reduxReactRouter } from 'redux-router'; 3 | import DevTools from '../containers/DevTools'; 4 | import createHistory from 'history/lib/createBrowserHistory'; 5 | import routes from '../routes'; 6 | import thunk from 'redux-thunk'; 7 | import createLogger from 'redux-logger'; 8 | import rootReducer from '../reducers'; 9 | 10 | import { logger, api } from "../middleware" 11 | 12 | const finalCreateStore = compose( 13 | applyMiddleware(api), 14 | applyMiddleware(thunk), 15 | reduxReactRouter({ routes, createHistory }), 16 | applyMiddleware(createLogger()), 17 | DevTools.instrument() 18 | )(createStore); 19 | 20 | export default function configureStore(initialState) { 21 | const store = finalCreateStore(rootReducer, initialState); 22 | 23 | if (module.hot) { 24 | // Enable Webpack hot module replacement for reducers 25 | module.hot.accept('../reducers', () => { 26 | const nextRootReducer = require('../reducers'); 27 | store.replaceReducer(nextRootReducer); 28 | }); 29 | } 30 | 31 | return store; 32 | } 33 | -------------------------------------------------------------------------------- /store/configureStore.js: -------------------------------------------------------------------------------- 1 | if (process.env.NODE_ENV === 'production') { 2 | module.exports = require('./configureStore.prod'); 3 | } else { 4 | module.exports = require('./configureStore.dev'); 5 | } 6 | -------------------------------------------------------------------------------- /store/configureStore.prod.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware, compose } from 'redux'; 2 | import { reduxReactRouter } from 'redux-router'; 3 | import createHistory from 'history/lib/createBrowserHistory'; 4 | import routes from '../routes'; 5 | import thunk from 'redux-thunk'; 6 | import rootReducer from '../reducers'; 7 | 8 | const finalCreateStore = compose( 9 | applyMiddleware(thunk), 10 | reduxReactRouter({ routes, createHistory }) 11 | )(createStore); 12 | 13 | export default function configureStore(initialState) { 14 | return finalCreateStore(rootReducer, initialState); 15 | } 16 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpack = require('webpack'); 3 | 4 | module.exports = { 5 | devtool: 'cheap-module-eval-source-map', 6 | entry: [ 7 | 'webpack-hot-middleware/client', 8 | './index' 9 | ], 10 | output: { 11 | path: path.join(__dirname, 'dist'), 12 | filename: 'bundle.js', 13 | publicPath: '/static/' 14 | }, 15 | plugins: [ 16 | new webpack.optimize.OccurenceOrderPlugin(), 17 | new webpack.HotModuleReplacementPlugin(), 18 | new webpack.NoErrorsPlugin() 19 | ], 20 | module: { 21 | loaders: [{ 22 | test: /\.js$/, 23 | loaders: ['babel'], 24 | exclude: /node_modules/, 25 | include: __dirname 26 | }] 27 | } 28 | }; 29 | 30 | 31 | var reduxNodeModules = path.join(__dirname, 'node_modules'); 32 | var fs = require('fs'); 33 | if (fs.existsSync(reduxNodeModules)) { 34 | // Resolve Redux to source 35 | //module.exports.resolve = { alias: { 'redux': reduxSrc } }; 36 | // Compile Redux from source 37 | module.exports.module.loaders.push({ 38 | test: /\.js$/, 39 | loaders: ['babel'] 40 | }); 41 | } 42 | --------------------------------------------------------------------------------