From 4fde14038163226a03bbe819f4308ee915d2c99a Mon Sep 17 00:00:00 2001 From: Mihail Gumennii Date: Thu, 30 Aug 2018 16:45:17 -0700 Subject: [PATCH 1/6] initial redux setup --- package.json | 2 ++ src/index.js | 10 +++++++--- src/pages/Sample/Sample.js | 6 +++++- src/server/render.js | 10 +++++++--- src/state/actions.js | 4 ++++ src/state/reducers.js | 11 +++++++++++ src/state/store.js | 11 +++++++++++ yarn.lock | 28 +++++++++++++++++++++++++++- 8 files changed, 74 insertions(+), 8 deletions(-) create mode 100644 src/state/actions.js create mode 100644 src/state/reducers.js create mode 100644 src/state/store.js diff --git a/package.json b/package.json index cec8e46..032fe73 100644 --- a/package.json +++ b/package.json @@ -33,10 +33,12 @@ "react-emotion": "9.2.8", "react-helmet": "5.2.0", "react-hot-loader": "4.0.0", + "react-redux": "5.0.7", "react-router": "4.3.1", "react-router-config": "1.0.0-beta.4", "react-router-dom": "4.3.1", "react-universal-component": "3.0.0", + "redux": "4.0.0", "style-loader": "0.23.0", "webpack": "4.17.1", "webpack-cli": "3.1.0", diff --git a/src/index.js b/src/index.js index 5df2f89..6cde03c 100644 --- a/src/index.js +++ b/src/index.js @@ -4,13 +4,17 @@ import './assets/global.css' import React from 'react' import ReactDOM from 'react-dom' import { BrowserRouter } from 'react-router-dom' +import { Provider } from 'react-redux' import App from './components/App' +import store from './state/store' ReactDOM.hydrate( - - - , document.getElementById('app-root') + + + + + , document.getElementById('app-root') ) console.log('Environemnt is', process.env.NODE_ENV) diff --git a/src/pages/Sample/Sample.js b/src/pages/Sample/Sample.js index d11bd9b..ad5ce86 100644 --- a/src/pages/Sample/Sample.js +++ b/src/pages/Sample/Sample.js @@ -1,5 +1,6 @@ import React, { Component } from 'react' import { Helmet } from 'react-helmet' +import { connect } from 'react-redux' import styled, { css } from 'react-emotion' import styles from './styles.css' @@ -55,10 +56,13 @@ class Sample extends Component { Sample Text +
Message from Redux Store: {this.props.text}
) } } -export default Sample \ No newline at end of file +export default connect(state => ({ + text: state.text +}))(Sample) \ No newline at end of file diff --git a/src/server/render.js b/src/server/render.js index 500fa95..e141810 100644 --- a/src/server/render.js +++ b/src/server/render.js @@ -4,16 +4,20 @@ import { renderToString } from 'react-dom/server' import { Helmet } from 'react-helmet' import { flushChunkNames } from 'react-universal-component/server' import flushChunks from 'webpack-flush-chunks' +import { Provider } from 'react-redux' import App from '../components/App' +import store from '../state/store' export default ({ clientStats }) => (req, res) => { const context = {} const app = renderToString( - - - + + + + + ) const { js, styles, cssHash } = flushChunks(clientStats, { diff --git a/src/state/actions.js b/src/state/actions.js new file mode 100644 index 0000000..3a31672 --- /dev/null +++ b/src/state/actions.js @@ -0,0 +1,4 @@ +export const actionTest = text => ({ + type: 'TEST_ACTION', + text +}) \ No newline at end of file diff --git a/src/state/reducers.js b/src/state/reducers.js new file mode 100644 index 0000000..0f3613e --- /dev/null +++ b/src/state/reducers.js @@ -0,0 +1,11 @@ +export const testReducer = (state = {}, action) => { + switch(action.type) { + case 'TEST_ACTION': + return { + ...state, + text: action.text + } + default: + return state + } +} \ No newline at end of file diff --git a/src/state/store.js b/src/state/store.js new file mode 100644 index 0000000..2dbb50d --- /dev/null +++ b/src/state/store.js @@ -0,0 +1,11 @@ +import { createStore } from 'redux' +import { testReducer } from './reducers' + +const enhancer = typeof window == 'object' && + window.__REDUX_DEVTOOLS_EXTENSION__ && + window.__REDUX_DEVTOOLS_EXTENSION__() + +export default createStore( + testReducer, + enhancer +) \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 0556f08..0ccd533 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3041,7 +3041,7 @@ interpret@^1.1.0: version "1.1.0" resolved "http://verdaccio.myarecruiter.com/interpret/-/interpret-1.1.0.tgz#7ed1b1410c6a0e0f78cf95d3b8440c63f78b8614" -invariant@^2.2.1, invariant@^2.2.2, invariant@^2.2.4: +invariant@^2.0.0, invariant@^2.2.1, invariant@^2.2.2, invariant@^2.2.4: version "2.2.4" resolved "http://verdaccio.myarecruiter.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" dependencies: @@ -3433,6 +3433,10 @@ locate-path@^3.0.0: p-locate "^3.0.0" path-exists "^3.0.0" +lodash-es@^4.17.5: + version "4.17.10" + resolved "http://verdaccio.myarecruiter.com/lodash-es/-/lodash-es-4.17.10.tgz#62cd7104cdf5dd87f235a837f0ede0e8e5117e05" + lodash.camelcase@^4.3.0: version "4.3.0" resolved "http://verdaccio.myarecruiter.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" @@ -4726,6 +4730,17 @@ react-hot-loader@4.0.0: prop-types "^15.6.0" shallowequal "^1.0.2" +react-redux@5.0.7: + version "5.0.7" + resolved "http://verdaccio.myarecruiter.com/react-redux/-/react-redux-5.0.7.tgz#0dc1076d9afb4670f993ffaef44b8f8c1155a4c8" + dependencies: + hoist-non-react-statics "^2.5.0" + invariant "^2.0.0" + lodash "^4.17.5" + lodash-es "^4.17.5" + loose-envify "^1.1.0" + prop-types "^15.6.0" + react-router-config@1.0.0-beta.4: version "1.0.0-beta.4" resolved "http://verdaccio.myarecruiter.com/react-router-config/-/react-router-config-1.0.0-beta.4.tgz#d202496dd0eabdf06cf24eb0793031f6891eef01" @@ -4844,6 +4859,13 @@ reduce-css-calc@^2.0.0: css-unit-converter "^1.1.1" postcss-value-parser "^3.3.0" +redux@4.0.0: + version "4.0.0" + resolved "http://verdaccio.myarecruiter.com/redux/-/redux-4.0.0.tgz#aa698a92b729315d22b34a0553d7e6533555cc03" + dependencies: + loose-envify "^1.1.0" + symbol-observable "^1.2.0" + regenerate-unicode-properties@^7.0.0: version "7.0.0" resolved "http://verdaccio.myarecruiter.com/regenerate-unicode-properties/-/regenerate-unicode-properties-7.0.0.tgz#107405afcc4a190ec5ed450ecaa00ed0cafa7a4c" @@ -5530,6 +5552,10 @@ svgo@^1.0.0: unquote "~1.1.1" util.promisify "~1.0.0" +symbol-observable@^1.2.0: + version "1.2.0" + resolved "http://verdaccio.myarecruiter.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" + tapable@^1.0.0: version "1.0.0" resolved "http://verdaccio.myarecruiter.com/tapable/-/tapable-1.0.0.tgz#cbb639d9002eed9c6b5975eb20598d7936f1f9f2" From 95bcfcf8b9a43e3824410a7ba4cf41555f335f43 Mon Sep 17 00:00:00 2001 From: Mihail Gumennii Date: Fri, 31 Aug 2018 10:11:12 -0700 Subject: [PATCH 2/6] redux thunk, middlewares, hot reloading for reducers --- package.json | 3 +++ src/index.js | 4 +++- src/pages/Sample/Sample.js | 9 +++++++-- src/server/render.js | 6 +++++- src/server/server.js | 6 ++++++ src/state/actions.js | 24 +++++++++++++++++++++--- src/state/reducers.js | 21 +++++++++++++++++---- src/state/store.js | 29 ++++++++++++++++++++--------- yarn.lock | 26 ++++++++++++++++++++++++-- 9 files changed, 106 insertions(+), 22 deletions(-) diff --git a/package.json b/package.json index 032fe73..aeb72aa 100644 --- a/package.json +++ b/package.json @@ -11,10 +11,12 @@ }, "dependencies": { "@babel/runtime": "7.0.0", + "axios": "0.18.0", "babel-loader": "8.0.0", "babel-plugin-universal-import": "3.0.0", "brotli-webpack-plugin": "1.0.0", "compression-webpack-plugin": "1.1.12", + "cross-fetch": "2.2.2", "css-loader": "1.0.0", "emotion": "9.2.8", "express": "4.16.3", @@ -39,6 +41,7 @@ "react-router-dom": "4.3.1", "react-universal-component": "3.0.0", "redux": "4.0.0", + "redux-thunk": "2.3.0", "style-loader": "0.23.0", "webpack": "4.17.1", "webpack-cli": "3.1.0", diff --git a/src/index.js b/src/index.js index 6cde03c..a5f23f9 100644 --- a/src/index.js +++ b/src/index.js @@ -7,7 +7,9 @@ import { BrowserRouter } from 'react-router-dom' import { Provider } from 'react-redux' import App from './components/App' -import store from './state/store' +import configureStore from './state/store' + +const store = configureStore({}) ReactDOM.hydrate( diff --git a/src/pages/Sample/Sample.js b/src/pages/Sample/Sample.js index ad5ce86..f68428e 100644 --- a/src/pages/Sample/Sample.js +++ b/src/pages/Sample/Sample.js @@ -3,6 +3,7 @@ import { Helmet } from 'react-helmet' import { connect } from 'react-redux' import styled, { css } from 'react-emotion' import styles from './styles.css' +import { fetchInitialData } from '../../state/actions'; // Example of dynamic imports const getLodash = () => { @@ -32,6 +33,10 @@ class Sample extends Component { } } + componentDidMount() { + this.props.dispatch(fetchInitialData('test')) + } + increment() { this.setState({ count: this.state.count + 1 @@ -56,7 +61,7 @@ class Sample extends Component { Sample Text -
Message from Redux Store: {this.props.text}
+
Message from Redux Store: {this.props.data.data} {this.props.data.test}
) @@ -64,5 +69,5 @@ class Sample extends Component { } export default connect(state => ({ - text: state.text + data: state.data }))(Sample) \ No newline at end of file diff --git a/src/server/render.js b/src/server/render.js index e141810..67c8f31 100644 --- a/src/server/render.js +++ b/src/server/render.js @@ -7,11 +7,15 @@ import flushChunks from 'webpack-flush-chunks' import { Provider } from 'react-redux' import App from '../components/App' -import store from '../state/store' +import configureStore from '../state/store' +import { fetchInitialData } from '../state/actions' export default ({ clientStats }) => (req, res) => { const context = {} + const store = configureStore() + store.dispatch(fetchInitialData()) + const app = renderToString( diff --git a/src/server/server.js b/src/server/server.js index 03939bc..f33af80 100644 --- a/src/server/server.js +++ b/src/server/server.js @@ -17,6 +17,12 @@ const DEV = process.env.NODE_ENV === 'development' const publicPath = configDevClient.output.publicPath const outputPath = configDevClient.output.path +server.get('/json', (req, res) => { + res.json({ + "data": "Misha" + }) +}) + if (DEV) { const compiler = webpack([configDevClient, configDevServer]) const clientCompiler = compiler.compilers[0] diff --git a/src/state/actions.js b/src/state/actions.js index 3a31672..b7435c1 100644 --- a/src/state/actions.js +++ b/src/state/actions.js @@ -1,4 +1,22 @@ -export const actionTest = text => ({ - type: 'TEST_ACTION', - text +import fetch from 'cross-fetch' + +// Example +export const fetchInitialData = () => dispatch => { + // return fetch('https://api.github.com/search/repositories?q=stars:>1+language:all&sort=stars&order=desc&type=Repositories') + return fetch('http://localhost:3000/json') + .then(res => res.json()) + .then(data => dispatch(fetchSuccess(data))) + .catch(err => dispatch(fetchFailure(err))) +} + +export const FETCH_SUCCESS = 'FETCH_SUCCESS' +export const fetchSuccess = response => ({ + type: FETCH_SUCCESS, + payload: response +}) + +export const FETCH_FAILURE = 'FETCH_FAILURE' +export const fetchFailure = error => ({ + type: FETCH_FAILURE, + payload: error }) \ No newline at end of file diff --git a/src/state/reducers.js b/src/state/reducers.js index 0f3613e..2f7607e 100644 --- a/src/state/reducers.js +++ b/src/state/reducers.js @@ -1,11 +1,24 @@ -export const testReducer = (state = {}, action) => { +import { + FETCH_SUCCESS, + FETCH_FAILURE +} from './actions' + +export const fetchInitialData = (state = {}, action) => { switch(action.type) { - case 'TEST_ACTION': + case FETCH_SUCCESS: { + action.payload.test = 'Boom' return { ...state, - text: action.text + data: action.payload } - default: + } + case FETCH_FAILURE: { + return { + ...state, + error: action.payload + } + } + default: return state } } \ No newline at end of file diff --git a/src/state/store.js b/src/state/store.js index 2dbb50d..bc97267 100644 --- a/src/state/store.js +++ b/src/state/store.js @@ -1,11 +1,22 @@ -import { createStore } from 'redux' -import { testReducer } from './reducers' +import { createStore, applyMiddleware, compose } from "redux" +import { fetchInitialData } from "./reducers" +import thunk from "redux-thunk" -const enhancer = typeof window == 'object' && - window.__REDUX_DEVTOOLS_EXTENSION__ && - window.__REDUX_DEVTOOLS_EXTENSION__() +const composeEnhancers = + typeof window === "object" && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ + ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) + : compose -export default createStore( - testReducer, - enhancer -) \ No newline at end of file +const enhancer = composeEnhancers(applyMiddleware(thunk)) + +export default initialState => { + const store = createStore(fetchInitialData, initialState, enhancer) + + if (process.env.NODE_ENV !== 'production' && module.hot) { + module.hot.accept('./reducers', () => + store.replaceReducer(require('./reducers').fetchInitialData) + ) + } + + return store +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 0ccd533..16433ed 100644 --- a/yarn.lock +++ b/yarn.lock @@ -980,6 +980,13 @@ atob@^2.1.1: version "2.1.2" resolved "http://verdaccio.myarecruiter.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" +axios@0.18.0: + version "0.18.0" + resolved "http://verdaccio.myarecruiter.com/axios/-/axios-0.18.0.tgz#32d53e4851efdc0a11993b6cd000789d70c05102" + dependencies: + follow-redirects "^1.3.0" + is-buffer "^1.1.5" + babel-code-frame@^6.26.0: version "6.26.0" resolved "http://verdaccio.myarecruiter.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" @@ -1743,6 +1750,13 @@ create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: safe-buffer "^5.0.1" sha.js "^2.4.8" +cross-fetch@2.2.2: + version "2.2.2" + resolved "http://verdaccio.myarecruiter.com/cross-fetch/-/cross-fetch-2.2.2.tgz#a47ff4f7fc712daba8f6a695a11c948440d45723" + dependencies: + node-fetch "2.1.2" + whatwg-fetch "2.0.4" + cross-spawn@^5.0.1: version "5.1.0" resolved "http://verdaccio.myarecruiter.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" @@ -2548,7 +2562,7 @@ flush-write-stream@^1.0.0: inherits "^2.0.1" readable-stream "^2.0.4" -follow-redirects@^1.0.0: +follow-redirects@^1.0.0, follow-redirects@^1.3.0: version "1.5.7" resolved "http://verdaccio.myarecruiter.com/follow-redirects/-/follow-redirects-1.5.7.tgz#a39e4804dacb90202bca76a9e2ac10433ca6a69a" dependencies: @@ -3782,6 +3796,10 @@ node-abi@^2.2.0: dependencies: semver "^5.4.1" +node-fetch@2.1.2: + version "2.1.2" + resolved "http://verdaccio.myarecruiter.com/node-fetch/-/node-fetch-2.1.2.tgz#ab884e8e7e57e38a944753cec706f788d1768bb5" + node-fetch@^1.0.1: version "1.7.3" resolved "http://verdaccio.myarecruiter.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef" @@ -4859,6 +4877,10 @@ reduce-css-calc@^2.0.0: css-unit-converter "^1.1.1" postcss-value-parser "^3.3.0" +redux-thunk@2.3.0: + version "2.3.0" + resolved "http://verdaccio.myarecruiter.com/redux-thunk/-/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622" + redux@4.0.0: version "4.0.0" resolved "http://verdaccio.myarecruiter.com/redux/-/redux-4.0.0.tgz#aa698a92b729315d22b34a0553d7e6533555cc03" @@ -6081,7 +6103,7 @@ websocket-extensions@>=0.1.1: version "0.1.3" resolved "http://verdaccio.myarecruiter.com/websocket-extensions/-/websocket-extensions-0.1.3.tgz#5d2ff22977003ec687a4b87073dfbbac146ccf29" -whatwg-fetch@>=0.10.0: +whatwg-fetch@2.0.4, whatwg-fetch@>=0.10.0: version "2.0.4" resolved "http://verdaccio.myarecruiter.com/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz#dde6a5df315f9d39991aa17621853d720b85566f" From bff4cc2db6f3f8fb8ab55f039c408f7be5131ed4 Mon Sep 17 00:00:00 2001 From: Mihail Gumennii Date: Fri, 31 Aug 2018 14:15:55 -0700 Subject: [PATCH 3/6] add if statement temp fix --- src/pages/Sample/Sample.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/pages/Sample/Sample.js b/src/pages/Sample/Sample.js index f68428e..79ce876 100644 --- a/src/pages/Sample/Sample.js +++ b/src/pages/Sample/Sample.js @@ -61,7 +61,9 @@ class Sample extends Component { Sample Text -
Message from Redux Store: {this.props.data.data} {this.props.data.test}
+ {this.props.data && +
Message from Redux Store: {this.props.data.data} {this.props.data.test}
+ } ) From f6c5b8a73cab80208fc4bd0b1e3bdd10f4ce9d50 Mon Sep 17 00:00:00 2001 From: Mihail Gumennii Date: Fri, 31 Aug 2018 16:55:16 -0700 Subject: [PATCH 4/6] redux initial data loading testing --- config/webpack.dev-server.js | 10 ++++- config/webpack.prod-server.js | 8 +++- src/index.js | 6 ++- src/pages/Sample/Sample.js | 20 ++++++--- src/routes/Routes.js | 8 ++-- src/server/render.js | 71 +++++++++++++++++------------- src/server/server.js | 11 +---- src/state/actions.js | 48 ++++++++++++-------- src/state/reducers.js | 24 ---------- src/state/reducers/index.js | 6 +++ src/state/reducers/usersReducer.js | 13 ++++++ src/state/store.js | 22 --------- src/state/store/createStore.js | 22 +++++++++ 13 files changed, 149 insertions(+), 120 deletions(-) delete mode 100644 src/state/reducers.js create mode 100644 src/state/reducers/index.js create mode 100644 src/state/reducers/usersReducer.js delete mode 100644 src/state/store.js create mode 100644 src/state/store/createStore.js diff --git a/config/webpack.dev-server.js b/config/webpack.dev-server.js index 51edd9c..3aa8c0c 100644 --- a/config/webpack.dev-server.js +++ b/config/webpack.dev-server.js @@ -1,6 +1,7 @@ const path = require('path') const webpack = require('webpack') const ExtractCssChunks = require('extract-css-chunks-webpack-plugin') +const nodeExternals = require('webpack-node-externals') const externals = require('./node-externals') // Required to skip /node_modules/ folder @@ -8,7 +9,11 @@ module.exports = { name: 'server', mode: 'production', target: 'node', - externals: externals, + externals: [nodeExternals({ + whitelist: [ + /babel-plugin-universal-import|react-universal-component/ + ] + })], entry: [ '@babel/plugin-transform-runtime', './src/server/render.js' @@ -47,7 +52,8 @@ module.exports = { maxChunks: 1 }), new webpack.EnvironmentPlugin({ - NODE_ENV: 'development' + NODE_ENV: 'development', + __isBrowser__: 'true' }) ] } \ No newline at end of file diff --git a/config/webpack.prod-server.js b/config/webpack.prod-server.js index 187ea08..13ded65 100644 --- a/config/webpack.prod-server.js +++ b/config/webpack.prod-server.js @@ -1,13 +1,17 @@ const path = require('path') const webpack = require('webpack') const ExtractCssChunks = require('extract-css-chunks-webpack-plugin') -const externals = require('./node-externals') // Required to skip /node_modules/ folder +const nodeExternals = require('webpack-node-externals') module.exports = { name: 'server', mode: 'production', target: 'node', - externals: externals, + externals: [nodeExternals({ + whitelist: [ + /babel-plugin-universal-import|react-universal-component/ + ] + })], entry: './src/server/render.js', output: { filename: 'prod-server-bundle.js', diff --git a/src/index.js b/src/index.js index a5f23f9..b0cbc92 100644 --- a/src/index.js +++ b/src/index.js @@ -7,9 +7,11 @@ import { BrowserRouter } from 'react-router-dom' import { Provider } from 'react-redux' import App from './components/App' -import configureStore from './state/store' +import configureStore from './state/store/createStore' -const store = configureStore({}) +const initialState = window.__INITIAL_STATE__ +delete window.__INITIAL_STATE__ +const store = configureStore(initialState) ReactDOM.hydrate( diff --git a/src/pages/Sample/Sample.js b/src/pages/Sample/Sample.js index 79ce876..7d27b9f 100644 --- a/src/pages/Sample/Sample.js +++ b/src/pages/Sample/Sample.js @@ -3,7 +3,7 @@ import { Helmet } from 'react-helmet' import { connect } from 'react-redux' import styled, { css } from 'react-emotion' import styles from './styles.css' -import { fetchInitialData } from '../../state/actions'; +import { fetchUsers } from '../../state/actions' // Example of dynamic imports const getLodash = () => { @@ -34,7 +34,7 @@ class Sample extends Component { } componentDidMount() { - this.props.dispatch(fetchInitialData('test')) + this.props.fetchUsers() } increment() { @@ -43,6 +43,12 @@ class Sample extends Component { }) } + renderUsers() { + return this.props.users.map(user => ( +
  • {user.name}
  • + )) + } + render() { return (
    @@ -61,9 +67,9 @@ class Sample extends Component {
    Sample Text - {this.props.data && -
    Message from Redux Store: {this.props.data.data} {this.props.data.test}
    - } +
      + {this.renderUsers()} +
    ) @@ -71,5 +77,5 @@ class Sample extends Component { } export default connect(state => ({ - data: state.data -}))(Sample) \ No newline at end of file + users: state.users +}), { fetchUsers })(Sample) \ No newline at end of file diff --git a/src/routes/Routes.js b/src/routes/Routes.js index b1562d1..1564d9d 100644 --- a/src/routes/Routes.js +++ b/src/routes/Routes.js @@ -2,11 +2,12 @@ import React from 'react' import universal from 'react-universal-component' import Home from '../pages/Home' -// import Sample from '../pages/Sample' + +import { fetchUsers } from '../state/actions' const Sample = universal(() => import('../pages/Sample'), { minDelay: 1200 -}); +}) export default [ { @@ -17,6 +18,7 @@ export default [ { path: '/sample', exact: true, - component: Sample + component: Sample, + getInitialData: store => store.dispatch(fetchUsers()) } ] \ No newline at end of file diff --git a/src/server/render.js b/src/server/render.js index 67c8f31..10d12f9 100644 --- a/src/server/render.js +++ b/src/server/render.js @@ -5,44 +5,55 @@ import { Helmet } from 'react-helmet' import { flushChunkNames } from 'react-universal-component/server' import flushChunks from 'webpack-flush-chunks' import { Provider } from 'react-redux' +import { matchRoutes } from 'react-router-config' +import serialize from 'serialize-javascript' +import Routes from '../routes' import App from '../components/App' -import configureStore from '../state/store' -import { fetchInitialData } from '../state/actions' +import createStore from '../state/store/createStore' export default ({ clientStats }) => (req, res) => { const context = {} - const store = configureStore() - store.dispatch(fetchInitialData()) - - const app = renderToString( - - - - - - ) + const helmet = Helmet.renderStatic() - const { js, styles, cssHash } = flushChunks(clientStats, { - chunkNames: flushChunkNames() + const store = createStore() + + // Get all matched routes and `getInitialData` + const promises = matchRoutes(Routes, req.path).map(({ route }) => { + return route.getInitialData ? route.getInitialData(store) : null }) - const helmet = Helmet.renderStatic() + Promise.all(promises).then(() => { + const app = renderToString( + + + + + + ) - res.send(` - - - - ${helmet.title.toString()} - ${helmet.meta.toString()} - ${styles} - - -
    ${app}
    - ${cssHash} - ${js} - - - `) + const { js, styles, cssHash } = flushChunks(clientStats, { + chunkNames: flushChunkNames() + }) + + res.send(` + + + + ${helmet.title.toString()} + ${helmet.meta.toString()} + ${styles} + + +
    ${app}
    + + ${cssHash} + ${js} + + + `) + }) } \ No newline at end of file diff --git a/src/server/server.js b/src/server/server.js index f33af80..f2cdae1 100644 --- a/src/server/server.js +++ b/src/server/server.js @@ -15,13 +15,6 @@ const server = express() const DEV = process.env.NODE_ENV === 'development' const publicPath = configDevClient.output.publicPath -const outputPath = configDevClient.output.path - -server.get('/json', (req, res) => { - res.json({ - "data": "Misha" - }) -}) if (DEV) { const compiler = webpack([configDevClient, configDevServer]) @@ -38,9 +31,7 @@ if (DEV) { } else { webpack([configProdClient, configProdServer]).run((err, stats) => { console.log( - stats.toString({ - colors: true - }) + stats.toString({ colors: true }) ) const clientStats = stats.toJson().children[0] diff --git a/src/state/actions.js b/src/state/actions.js index b7435c1..804994a 100644 --- a/src/state/actions.js +++ b/src/state/actions.js @@ -1,22 +1,34 @@ import fetch from 'cross-fetch' +import axios from 'axios' -// Example -export const fetchInitialData = () => dispatch => { - // return fetch('https://api.github.com/search/repositories?q=stars:>1+language:all&sort=stars&order=desc&type=Repositories') - return fetch('http://localhost:3000/json') - .then(res => res.json()) - .then(data => dispatch(fetchSuccess(data))) - .catch(err => dispatch(fetchFailure(err))) -} +// // Example +// export const fetchInitialData = () => dispatch => { +// // return fetch('https://api.github.com/search/repositories?q=stars:>1+language:all&sort=stars&order=desc&type=Repositories') +// return fetch('http://localhost:3000/json') +// .then(res => res.json()) +// .then(data => dispatch(fetchSuccess(data))) +// .catch(err => dispatch(fetchFailure(err))) +// } -export const FETCH_SUCCESS = 'FETCH_SUCCESS' -export const fetchSuccess = response => ({ - type: FETCH_SUCCESS, - payload: response -}) +// export const FETCH_SUCCESS = 'FETCH_SUCCESS' +// export const fetchSuccess = response => ({ +// type: FETCH_SUCCESS, +// payload: response +// }) -export const FETCH_FAILURE = 'FETCH_FAILURE' -export const fetchFailure = error => ({ - type: FETCH_FAILURE, - payload: error -}) \ No newline at end of file +// export const FETCH_FAILURE = 'FETCH_FAILURE' +// export const fetchFailure = error => ({ +// type: FETCH_FAILURE, +// payload: error +// }) + +export const FETCH_USERS = 'fetch_users' +export const fetchUsers = () => async dispatch => { + // with a dispatch we can pass some global defined API + const res = await axios.get('http://react-ssr-api.herokuapp.com/users') + + dispatch({ + type: FETCH_USERS, + payload: res + }) +} \ No newline at end of file diff --git a/src/state/reducers.js b/src/state/reducers.js deleted file mode 100644 index 2f7607e..0000000 --- a/src/state/reducers.js +++ /dev/null @@ -1,24 +0,0 @@ -import { - FETCH_SUCCESS, - FETCH_FAILURE -} from './actions' - -export const fetchInitialData = (state = {}, action) => { - switch(action.type) { - case FETCH_SUCCESS: { - action.payload.test = 'Boom' - return { - ...state, - data: action.payload - } - } - case FETCH_FAILURE: { - return { - ...state, - error: action.payload - } - } - default: - return state - } -} \ No newline at end of file diff --git a/src/state/reducers/index.js b/src/state/reducers/index.js new file mode 100644 index 0000000..f000667 --- /dev/null +++ b/src/state/reducers/index.js @@ -0,0 +1,6 @@ +import { combineReducers } from 'redux' +import usersReducer from './usersReducer' + +export default combineReducers({ + users: usersReducer +}) \ No newline at end of file diff --git a/src/state/reducers/usersReducer.js b/src/state/reducers/usersReducer.js new file mode 100644 index 0000000..8f586a3 --- /dev/null +++ b/src/state/reducers/usersReducer.js @@ -0,0 +1,13 @@ +import { + FETCH_USERS +} from '../actions' + +export default (state = [], action) => { + switch(action.type) { + case FETCH_USERS: { + return action.payload.data + } + default: + return state + } +} \ No newline at end of file diff --git a/src/state/store.js b/src/state/store.js deleted file mode 100644 index bc97267..0000000 --- a/src/state/store.js +++ /dev/null @@ -1,22 +0,0 @@ -import { createStore, applyMiddleware, compose } from "redux" -import { fetchInitialData } from "./reducers" -import thunk from "redux-thunk" - -const composeEnhancers = - typeof window === "object" && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ - ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) - : compose - -const enhancer = composeEnhancers(applyMiddleware(thunk)) - -export default initialState => { - const store = createStore(fetchInitialData, initialState, enhancer) - - if (process.env.NODE_ENV !== 'production' && module.hot) { - module.hot.accept('./reducers', () => - store.replaceReducer(require('./reducers').fetchInitialData) - ) - } - - return store -} \ No newline at end of file diff --git a/src/state/store/createStore.js b/src/state/store/createStore.js new file mode 100644 index 0000000..fe4a54a --- /dev/null +++ b/src/state/store/createStore.js @@ -0,0 +1,22 @@ +import { createStore, applyMiddleware, compose } from 'redux' +import reducers from '../reducers' +import thunk from 'redux-thunk' + +const composeEnhancers = + typeof window === 'object' && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ + ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) + : compose + +const enhancer = composeEnhancers(applyMiddleware(thunk)) + +export default initialState => { + const store = createStore(reducers, initialState, enhancer) + + if (process.env.NODE_ENV !== 'production' && module.hot) { + module.hot.accept('../reducers', () => + store.replaceReducer(require('../reducers')) + ) + } + + return store +} \ No newline at end of file From ceda1e2b782f99ad2d122d07ed90d2ac3c6bf1a0 Mon Sep 17 00:00:00 2001 From: Mihail Gumennii Date: Sat, 1 Sep 2018 11:27:26 -0700 Subject: [PATCH 5/6] save --- config/webpack.dev-server.js | 8 ------ src/index.js | 4 +-- src/pages/Sample/Sample.js | 10 ++++--- src/server/server.js | 2 +- src/state/actions.js | 43 ++++++++++++++++++------------ src/state/reducers/usersReducer.js | 38 +++++++++++++++++++++++--- 6 files changed, 69 insertions(+), 36 deletions(-) diff --git a/config/webpack.dev-server.js b/config/webpack.dev-server.js index 3aa8c0c..ebedbc0 100644 --- a/config/webpack.dev-server.js +++ b/config/webpack.dev-server.js @@ -1,19 +1,11 @@ const path = require('path') const webpack = require('webpack') const ExtractCssChunks = require('extract-css-chunks-webpack-plugin') -const nodeExternals = require('webpack-node-externals') - -const externals = require('./node-externals') // Required to skip /node_modules/ folder module.exports = { name: 'server', mode: 'production', target: 'node', - externals: [nodeExternals({ - whitelist: [ - /babel-plugin-universal-import|react-universal-component/ - ] - })], entry: [ '@babel/plugin-transform-runtime', './src/server/render.js' diff --git a/src/index.js b/src/index.js index b0cbc92..35b520f 100644 --- a/src/index.js +++ b/src/index.js @@ -19,6 +19,4 @@ ReactDOM.hydrate(
    , document.getElementById('app-root') -) - -console.log('Environemnt is', process.env.NODE_ENV) +) \ No newline at end of file diff --git a/src/pages/Sample/Sample.js b/src/pages/Sample/Sample.js index 7d27b9f..bc7f600 100644 --- a/src/pages/Sample/Sample.js +++ b/src/pages/Sample/Sample.js @@ -34,7 +34,11 @@ class Sample extends Component { } componentDidMount() { - this.props.fetchUsers() + const { users: { isFetched } } = this.props + + if (!isFetched) { + this.props.fetchUsers() + } } increment() { @@ -67,9 +71,9 @@ class Sample extends Component { Sample Text -
      + {/*
        {this.renderUsers()} -
      +
    */} ) diff --git a/src/server/server.js b/src/server/server.js index f2cdae1..bf184d1 100644 --- a/src/server/server.js +++ b/src/server/server.js @@ -51,5 +51,5 @@ if (DEV) { } server.listen(3000, () => { - console.log('Listening on prot 3000') + console.log('Listening on port 3000') }) \ No newline at end of file diff --git a/src/state/actions.js b/src/state/actions.js index 804994a..dc28131 100644 --- a/src/state/actions.js +++ b/src/state/actions.js @@ -1,4 +1,3 @@ -import fetch from 'cross-fetch' import axios from 'axios' // // Example @@ -10,25 +9,35 @@ import axios from 'axios' // .catch(err => dispatch(fetchFailure(err))) // } -// export const FETCH_SUCCESS = 'FETCH_SUCCESS' -// export const fetchSuccess = response => ({ -// type: FETCH_SUCCESS, -// payload: response -// }) +export const FETCH_USERS_REQUEST = 'fetchUsersRequest' +export const FETCH_USERS_SUCCESS = 'fetchUsersSuccess' +export const FETCH_USERS_FAILURE = 'fetchUsersFailure' +export const FETCH_USERS = 'fetchUsers' -// export const FETCH_FAILURE = 'FETCH_FAILURE' -// export const fetchFailure = error => ({ -// type: FETCH_FAILURE, -// payload: error -// }) +export const fetchUsersRequest = response => ({ + type: FETCH_USERS_REQUEST +}) + +export const fetchUsersSuccess = response => ({ + type: FETCH_USERS_SUCCESS, + payload: response +}) + +export const fetchUsersFailure = error => ({ + type: FETCH_FAILURE, + payload: error +}) -export const FETCH_USERS = 'fetch_users' export const fetchUsers = () => async dispatch => { + console.log('fetching users') // with a dispatch we can pass some global defined API - const res = await axios.get('http://react-ssr-api.herokuapp.com/users') + // dispatch(fetchUsersRequest()) - dispatch({ - type: FETCH_USERS, - payload: res - }) + try { + const response = await axios.get('http://react-ssr-api.herokuapp.com/users') + console.log(response) + dispatch(fetchUsersSuccess(response)) + } catch (err) { + dispatch(fetchUsersFailure(err)) + } } \ No newline at end of file diff --git a/src/state/reducers/usersReducer.js b/src/state/reducers/usersReducer.js index 8f586a3..c2afa7b 100644 --- a/src/state/reducers/usersReducer.js +++ b/src/state/reducers/usersReducer.js @@ -1,11 +1,41 @@ import { - FETCH_USERS + FETCH_USERS_REQUEST, + FETCH_USERS_SUCCESS, + FETCH_USERS_FAILURE } from '../actions' -export default (state = [], action) => { +const defaultState = { + users: [], + isFetching: false, + isFetched: false, + error: null +} + +export default (state = defaultState, action) => { + console.log(action.payload) switch(action.type) { - case FETCH_USERS: { - return action.payload.data + case FETCH_USERS_REQUEST: { + return { + ...state, + isFetching: true, + isFetched: false + } + } + case FETCH_USERS_SUCCESS: { + return { + ...state, + users: action.users, + isFetching: false, + isFetched: true + } + } + case FETCH_USERS_FAILURE: { + return { + ...state, + error: action.error, + isFetching: false, + isFetched: false + } } default: return state From 4d274588900bde551a5a6b10efef0edc2cf5765c Mon Sep 17 00:00:00 2001 From: Mihail Gumennii Date: Mon, 3 Sep 2018 22:09:11 -0700 Subject: [PATCH 6/6] play with different fetch states --- src/pages/Sample/Sample.js | 16 +++++----- src/routes/Routes.js | 4 +-- src/server/server.js | 15 ++++++++-- src/state/actions.js | 47 +++++++++++++++++------------- src/state/reducers/usersReducer.js | 15 +++++----- src/state/store/createStore.js | 2 +- 6 files changed, 56 insertions(+), 43 deletions(-) diff --git a/src/pages/Sample/Sample.js b/src/pages/Sample/Sample.js index bc7f600..e68de6f 100644 --- a/src/pages/Sample/Sample.js +++ b/src/pages/Sample/Sample.js @@ -3,7 +3,7 @@ import { Helmet } from 'react-helmet' import { connect } from 'react-redux' import styled, { css } from 'react-emotion' import styles from './styles.css' -import { fetchUsers } from '../../state/actions' +import { fetchData } from '../../state/actions' // Example of dynamic imports const getLodash = () => { @@ -34,10 +34,10 @@ class Sample extends Component { } componentDidMount() { - const { users: { isFetched } } = this.props + const { data: { isFetched } } = this.props if (!isFetched) { - this.props.fetchUsers() + // this.props.fetchData() } } @@ -48,7 +48,7 @@ class Sample extends Component { } renderUsers() { - return this.props.users.map(user => ( + return this.props.data.users.map(user => (
  • {user.name}
  • )) } @@ -71,9 +71,9 @@ class Sample extends Component { Sample Text - {/*
      +
        {this.renderUsers()} -
      */} +
    ) @@ -81,5 +81,5 @@ class Sample extends Component { } export default connect(state => ({ - users: state.users -}), { fetchUsers })(Sample) \ No newline at end of file + data: state.users +}), { fetchData })(Sample) \ No newline at end of file diff --git a/src/routes/Routes.js b/src/routes/Routes.js index 1564d9d..7b678d9 100644 --- a/src/routes/Routes.js +++ b/src/routes/Routes.js @@ -3,7 +3,7 @@ import universal from 'react-universal-component' import Home from '../pages/Home' -import { fetchUsers } from '../state/actions' +import { fetchData } from '../state/actions' const Sample = universal(() => import('../pages/Sample'), { minDelay: 1200 @@ -19,6 +19,6 @@ export default [ path: '/sample', exact: true, component: Sample, - getInitialData: store => store.dispatch(fetchUsers()) + getInitialData: store => store.dispatch(fetchData()) } ] \ No newline at end of file diff --git a/src/server/server.js b/src/server/server.js index bf184d1..7448df6 100644 --- a/src/server/server.js +++ b/src/server/server.js @@ -16,6 +16,15 @@ const server = express() const DEV = process.env.NODE_ENV === 'development' const publicPath = configDevClient.output.publicPath +server.get('/json', (req, res) => { + res.json({ + users: [ + { name: 'Shalom' }, + { name: 'Misha' } + ] + }) +}) + if (DEV) { const compiler = webpack([configDevClient, configDevServer]) const clientCompiler = compiler.compilers[0] @@ -26,7 +35,7 @@ if (DEV) { server.use(webpackDevMiddleware(compiler, options)) server.use(webpackHotMiddleware(clientCompiler)) server.use(webpackHotServerMiddleware(compiler)) - console.log('Middleware enabled') + console.log(`🛠 Middleware Applied and Enabled`) } else { webpack([configProdClient, configProdServer]).run((err, stats) => { @@ -38,7 +47,7 @@ if (DEV) { const render = require('../../build/prod-server-bundle.js').default // Letting Express server to serve files from 'dist' folder - const staticMiddleware = express.static('dist') + // const staticMiddleware = express.static('dist') server.use( expressStaticGzip('dist', { enableBrotli: true @@ -51,5 +60,5 @@ if (DEV) { } server.listen(3000, () => { - console.log('Listening on port 3000') + console.log('🚀 Server is Started and Listening on port 3000') }) \ No newline at end of file diff --git a/src/state/actions.js b/src/state/actions.js index dc28131..0847d0a 100644 --- a/src/state/actions.js +++ b/src/state/actions.js @@ -9,35 +9,40 @@ import axios from 'axios' // .catch(err => dispatch(fetchFailure(err))) // } -export const FETCH_USERS_REQUEST = 'fetchUsersRequest' -export const FETCH_USERS_SUCCESS = 'fetchUsersSuccess' -export const FETCH_USERS_FAILURE = 'fetchUsersFailure' -export const FETCH_USERS = 'fetchUsers' +export const FETCH_DATA_REQUEST = 'fetchDataRequest' +export const FETCH_DATA_SUCCESS = 'fetchDataSuccess' +export const FETCH_DATA_FAILURE = 'fetchDataFailure' +export const FETCH_DATA = 'fetchData' -export const fetchUsersRequest = response => ({ - type: FETCH_USERS_REQUEST +export const fetchDataRequest = () => ({ + type: FETCH_DATA_REQUEST }) -export const fetchUsersSuccess = response => ({ - type: FETCH_USERS_SUCCESS, +export const fetchDataSuccess = response => ({ + type: FETCH_DATA_SUCCESS, payload: response }) -export const fetchUsersFailure = error => ({ - type: FETCH_FAILURE, +export const fetchDataFailure = error => ({ + type: FETCH_DATA_FAILURE, payload: error }) -export const fetchUsers = () => async dispatch => { - console.log('fetching users') +export const fetchData = () => async dispatch => { // with a dispatch we can pass some global defined API - // dispatch(fetchUsersRequest()) - - try { - const response = await axios.get('http://react-ssr-api.herokuapp.com/users') - console.log(response) - dispatch(fetchUsersSuccess(response)) - } catch (err) { - dispatch(fetchUsersFailure(err)) - } + dispatch(fetchDataRequest()) + + return fetch('http://localhost:3000/json') + .then(res => res.json()) + .then(data => dispatch(fetchDataSuccess(data))) + .catch(err => dispatch(fetchDataFailure(err))) + + // try { + // const response = await axios.get('/json') + // // const response = await axios.get('http://react-ssr-api.herokuapp.com/users') + // const data = await response.data + // dispatch(fetchDataSuccess(data)) + // } catch (err) { + // dispatch(fetchDataFailure(err)) + // } } \ No newline at end of file diff --git a/src/state/reducers/usersReducer.js b/src/state/reducers/usersReducer.js index c2afa7b..8207886 100644 --- a/src/state/reducers/usersReducer.js +++ b/src/state/reducers/usersReducer.js @@ -1,7 +1,7 @@ import { - FETCH_USERS_REQUEST, - FETCH_USERS_SUCCESS, - FETCH_USERS_FAILURE + FETCH_DATA_REQUEST, + FETCH_DATA_SUCCESS, + FETCH_DATA_FAILURE } from '../actions' const defaultState = { @@ -12,24 +12,23 @@ const defaultState = { } export default (state = defaultState, action) => { - console.log(action.payload) switch(action.type) { - case FETCH_USERS_REQUEST: { + case FETCH_DATA_REQUEST: { return { ...state, isFetching: true, isFetched: false } } - case FETCH_USERS_SUCCESS: { + case FETCH_DATA_SUCCESS: { return { ...state, - users: action.users, + users: action.payload.users, isFetching: false, isFetched: true } } - case FETCH_USERS_FAILURE: { + case FETCH_DATA_FAILURE: { return { ...state, error: action.error, diff --git a/src/state/store/createStore.js b/src/state/store/createStore.js index fe4a54a..27d00c9 100644 --- a/src/state/store/createStore.js +++ b/src/state/store/createStore.js @@ -14,7 +14,7 @@ export default initialState => { if (process.env.NODE_ENV !== 'production' && module.hot) { module.hot.accept('../reducers', () => - store.replaceReducer(require('../reducers')) + store.replaceReducer(require('../reducers').default) ) }