├── .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 |
25 |
26 |
27 |
28 |
{title}
29 |
30 |
31 | {message}
32 |
33 |
34 |
35 |
36 |
37 |
38 |
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 |
--------------------------------------------------------------------------------