diff --git a/README.md b/README.md
index 2558f6e..2f4879a 100644
--- a/README.md
+++ b/README.md
@@ -7,13 +7,18 @@ Install
-------
```bash
npm install
-npm run build
-npm run start:server
```
### Development
-This just runs create react app, its great!
+This runs the create react app with hot reloading containers + reducers and the api backend
```bash
npm start
```
+### Production
+-------
+```bash
+npm run build
+npm run now-start
+```
+
diff --git a/package.json b/package.json
index 1015de2..93a9cea 100644
--- a/package.json
+++ b/package.json
@@ -2,8 +2,10 @@
"name": "ssr-create-react-app-v2",
"version": "0.1.0",
"private": true,
+ "proxy": "http://localhost:3001/",
"devDependencies": {
"babel-cli": "^6.24.1",
+ "concurrently": "^3.4.0",
"react-scripts": "0.8.5"
},
"dependencies": {
@@ -16,12 +18,14 @@
"react-dom": "^15.5.4",
"react-redux": "^5.0.4",
"react-router-dom": "^4.1.1",
- "redux": "^3.6.0"
+ "redux": "^3.6.0",
+ "redux-saga": "^0.15.0"
},
"scripts": {
- "start": "react-scripts start",
+ "start": "./node_modules/.bin/concurrently \"npm run start:server\" \"npm run start:client\"",
+ "start:client": "react-scripts start",
"start:server": "NODE_ENV=development node server/index.js",
- "now-start": "NODE_ENV=production node server/index.js",
+ "now-start": "NODE_ENV=production PORT=3000 node server/index.js",
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
diff --git a/server/routes/api.js b/server/routes/api.js
index d33b22c..f4c21a2 100644
--- a/server/routes/api.js
+++ b/server/routes/api.js
@@ -10,5 +10,14 @@ router.get('/', function(req, res, next) {
res.json({})
})
+const users = {
+ 1: { email: 'test@test.com' }
+}
+router.get('/users/:id', function(req, res, next) {
+ if(Object.keys(users).indexOf(req.params.id) > -1) {
+ res.json(users[req.params.id])
+ } else {
+ res.status(404).json({})
+ }
+})
module.exports = router
-
diff --git a/src/actions/user.js b/src/actions/user.js
index d42e16b..9614bbe 100644
--- a/src/actions/user.js
+++ b/src/actions/user.js
@@ -1,4 +1,11 @@
-import { SET, RESET } from '../types/user'
+import { REQUEST, SET, RESET } from '../types/user'
+
+export function request(id) {
+ return {
+ type: REQUEST,
+ id
+ }
+}
export function set(payload){
return {
@@ -12,4 +19,3 @@ export function reset(){
type: RESET
}
}
-
diff --git a/src/api.js b/src/api.js
index 8860dab..a65efaf 100644
--- a/src/api.js
+++ b/src/api.js
@@ -7,8 +7,9 @@ class Api {
if (!options){
return
}
- const {token} = options
+ const {token, prefix} = options
this.token = token
+ this.prefix = prefix
}
getJsonHeaders(){
return {
@@ -34,11 +35,51 @@ class Api {
_buildQueryString(data){
return '?' + Object.keys(data).map(d=>d+'='+encodeURIComponent(data[d]))
}
+ parseJson(res) {
+ return res.json()
+ }
+ checkStatus(res) {
+ if (res.status >= 200 && res.status < 300) {
+ return res
+ } else {
+ var error = new Error(res.statusText)
+ error.response = res
+ throw error
+ }
+ }
+ get(fixture, id) {
+ if(id) {
+ return fetch(`${this.apiUrl}${this.prefix}/${fixture}/${id}`, this.getJsonHeaders())
+ .then(this.checkStatus)
+ .then(this.parseJson)
+ .then(data => {
+ return {ok: true, data}
+ }).catch(err => {
+ return {ok: false, err}
+ })
+ } else {
+ return fetch(`${this.apiUrl}${this.prefix}/${fixture}`, this.getJsonHeaders())
+ .then(this.checkStatus)
+ .then(this.parseJson)
+ .then(data => {
+ return {ok: true, data}
+ }).catch(err => {
+ return {ok: false, err}
+ })
+ }
+ }
}
-export class MainApi extends Api{
- constructor(options){
- super(options)
- this.prefix = '/api'
+const create = (prefix = '/api') => {
+ const api = new Api({prefix})
+
+ const getUser = (id) => api.get('users', id)
+
+ return {
+ getUser
}
}
+
+export default {
+ create
+}
diff --git a/src/config.js b/src/config.js
index d144172..f652f0f 100644
--- a/src/config.js
+++ b/src/config.js
@@ -1,4 +1,4 @@
-let apiUrl = 'http://localhost:3001'
+let apiUrl = 'http://localhost:3000'
if (process.env.NODE_ENV === 'production') {
apiUrl = ''
}
diff --git a/src/containers/SecondPage.js b/src/containers/SecondPage.js
index 2bd3704..74159e9 100644
--- a/src/containers/SecondPage.js
+++ b/src/containers/SecondPage.js
@@ -7,6 +7,9 @@ import { Link } from 'react-router-dom'
import './SecondPage.css'
class SecondPage extends Component {
+ componentDidMount() {
+ this.props.userActions.request(1)
+ }
render() {
return (
diff --git a/src/index.js b/src/index.js
index fb6bed7..2162548 100644
--- a/src/index.js
+++ b/src/index.js
@@ -11,13 +11,22 @@ import App from './containers/App'
const initialState = {}
const store = configureStore(initialState)
-ReactDOM.render(
-
-
-
-
-
-, document.getElementById('root')
-)
+const render = (Component) => {
+ ReactDOM.render(
+
+
+
+
+ ,
+ document.getElementById('root')
+ )
+}
+render(App)
+if(process.env.NODE_ENV === 'development' && module.hot) {
+ module.hot.accept('./containers/App', () => {
+ const NewApp = require('./containers/App').default
+ render(NewApp)
+ })
+}
diff --git a/src/sagas/index.js b/src/sagas/index.js
new file mode 100644
index 0000000..7314968
--- /dev/null
+++ b/src/sagas/index.js
@@ -0,0 +1,11 @@
+import { fork } from 'redux-saga/effects'
+import userSaga from './user'
+import API from '../api'
+
+const api = API.create()
+
+export default function * rootSaga() {
+ yield [
+ fork(userSaga(api))
+ ]
+}
diff --git a/src/sagas/user.js b/src/sagas/user.js
new file mode 100644
index 0000000..67daa6d
--- /dev/null
+++ b/src/sagas/user.js
@@ -0,0 +1,27 @@
+import * as userActions from '../actions/user'
+import * as userTypes from '../types/user'
+import { call, put, takeLatest } from 'redux-saga/effects'
+
+function * getUser(api, {id}) {
+ if(!id) {
+ yield put(userActions.reset())
+ } else {
+ const response = yield call(api.getUser, id)
+
+ if(response.ok) {
+ yield put(userActions.set(response.data))
+ } else {
+ yield put(userActions.reset())
+ }
+ }
+}
+
+const userSaga = (api) => {
+ return function * () {
+ yield [
+ takeLatest(userTypes.REQUEST, getUser, api)
+ ]
+ }
+}
+
+export default userSaga
diff --git a/src/store.js b/src/store.js
index 9891af9..68f7093 100644
--- a/src/store.js
+++ b/src/store.js
@@ -1,15 +1,16 @@
import { createStore, applyMiddleware, compose } from 'redux'
import reducers from './reducers'
+import rootSaga from './sagas'
//import createLogger from 'redux-logger'
-//import createSagaMiddleware from 'redux-saga'
+import createSagaMiddleware from 'redux-saga'
//const logger = createLogger()
-//const sagaMiddleware = createSagaMiddleware()
+const sagaMiddleware = createSagaMiddleware()
export default function configureStore(initialState = {}) {
// Create the store with two middlewares
const middlewares = [
- // sagaMiddleware
+ sagaMiddleware
//, logger
]
@@ -24,8 +25,14 @@ export default function configureStore(initialState = {}) {
)
// Extensions
- //store.runSaga = sagaMiddleware.run
+ sagaMiddleware.run(rootSaga)
store.asyncReducers = {} // Async reducer registry
+ if(process.env.NODE_ENV === 'development' && module.hot) {
+ module.hot.accept('./reducers', () => {
+ store.replaceReducer(require('./reducers').default)
+ })
+ }
+
return store
}
diff --git a/src/types/user.js b/src/types/user.js
index 43d3cfb..323f59c 100644
--- a/src/types/user.js
+++ b/src/types/user.js
@@ -1,5 +1,5 @@
const prefix = 'USER/'
+export const REQUEST = prefix + 'REQUEST'
export const SET = prefix + 'SET'
export const RESET = prefix + 'RESET'
-
diff --git a/yarn.lock b/yarn.lock
index 5bc0490..92bfd72 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -80,10 +80,18 @@ ansi-html@0.0.5:
version "0.0.5"
resolved "https://registry.yarnpkg.com/ansi-html/-/ansi-html-0.0.5.tgz#0dcaa5a081206866bc240a3b773a184ea3b88b64"
+ansi-regex@^0.2.0, ansi-regex@^0.2.1:
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-0.2.1.tgz#0d8e946967a3d8143f93e24e298525fc1b2235f9"
+
ansi-regex@^2.0.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df"
+ansi-styles@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-1.1.0.tgz#eaecbf66cd706882760b2f4691582b8f55d7a7de"
+
ansi-styles@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
@@ -1119,6 +1127,16 @@ center-align@^0.1.1:
align-text "^0.1.3"
lazy-cache "^1.0.3"
+chalk@0.5.1:
+ version "0.5.1"
+ resolved "https://registry.yarnpkg.com/chalk/-/chalk-0.5.1.tgz#663b3a648b68b55d04690d49167aa837858f2174"
+ dependencies:
+ ansi-styles "^1.1.0"
+ escape-string-regexp "^1.0.0"
+ has-ansi "^0.1.0"
+ strip-ansi "^0.3.0"
+ supports-color "^0.2.0"
+
chalk@1.1.3, chalk@^1.0.0, chalk@^1.1.0, chalk@^1.1.1, chalk@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
@@ -1267,6 +1285,10 @@ combined-stream@^1.0.5, combined-stream@~1.0.5:
dependencies:
delayed-stream "~1.0.0"
+commander@2.6.0:
+ version "2.6.0"
+ resolved "https://registry.yarnpkg.com/commander/-/commander-2.6.0.tgz#9df7e52fb2a0cb0fb89058ee80c3104225f37e1d"
+
commander@2.9.x, commander@^2.8.1:
version "2.9.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4"
@@ -1312,6 +1334,19 @@ concat-stream@^1.4.6:
readable-stream "^2.2.2"
typedarray "^0.0.6"
+concurrently@^3.4.0:
+ version "3.4.0"
+ resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-3.4.0.tgz#60662b3defde07375bae19aac0ab780ec748ba79"
+ dependencies:
+ chalk "0.5.1"
+ commander "2.6.0"
+ date-fns "^1.23.0"
+ lodash "^4.5.1"
+ rx "2.3.24"
+ spawn-command "^0.0.2-1"
+ supports-color "^3.2.3"
+ tree-kill "^1.1.0"
+
connect-history-api-fallback@1.3.0, connect-history-api-fallback@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.3.0.tgz#e51d17f8f0ef0db90a64fdb47de3051556e9f169"
@@ -1534,6 +1569,10 @@ dashdash@^1.12.0:
dependencies:
assert-plus "^1.0.0"
+date-fns@^1.23.0:
+ version "1.28.4"
+ resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.28.4.tgz#7938aec34ba31fc8bd134d2344bc2e0bbfd95165"
+
date-now@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b"
@@ -1796,7 +1835,7 @@ escape-html@~1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
-escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
+escape-string-regexp@1.0.5, escape-string-regexp@^1.0.0, escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
@@ -2385,6 +2424,12 @@ har-validator@~4.2.1:
ajv "^4.9.1"
har-schema "^1.0.5"
+has-ansi@^0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-0.1.0.tgz#84f265aae8c0e6a88a12d7022894b7568894c62e"
+ dependencies:
+ ansi-regex "^0.2.0"
+
has-ansi@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91"
@@ -2563,12 +2608,6 @@ ignore@^3.1.5:
version "3.2.7"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.2.7.tgz#4810ca5f1d8eca5595213a34b94f2eb4ed926bbd"
-import-export@^1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/import-export/-/import-export-1.0.1.tgz#2b705fc9d022a32cf86a43e41224b8e25ab6335e"
- dependencies:
- node-hook "^0.4.0"
-
imurmurhash@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
@@ -3342,7 +3381,7 @@ lodash.uniq@^4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
-"lodash@>=3.5 <5", lodash@^4.0.0, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.16.2, lodash@^4.16.4, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0:
+"lodash@>=3.5 <5", lodash@^4.0.0, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.16.2, lodash@^4.16.4, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0, lodash@^4.5.1:
version "4.17.4"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae"
@@ -3491,10 +3530,6 @@ mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkd
dependencies:
minimist "0.0.8"
-moment@^2.18.1:
- version "2.18.1"
- resolved "https://registry.yarnpkg.com/moment/-/moment-2.18.1.tgz#c36193dd3ce1c2eed2adb7c802dbbc77a81b1c0f"
-
morgan@^1.8.1:
version "1.8.1"
resolved "https://registry.yarnpkg.com/morgan/-/morgan-1.8.1.tgz#f93023d3887bd27b78dfd6023cea7892ee27a4b1"
@@ -3556,17 +3591,13 @@ node-emoji@^1.4.1:
dependencies:
string.prototype.codepointat "^0.2.0"
-node-fetch@^1.0.1, node-fetch@^1.6.3:
+node-fetch@^1.0.1:
version "1.6.3"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.6.3.tgz#dc234edd6489982d58e8f0db4f695029abcd8c04"
dependencies:
encoding "^0.1.11"
is-stream "^1.0.1"
-node-hook@^0.4.0:
- version "0.4.0"
- resolved "https://registry.yarnpkg.com/node-hook/-/node-hook-0.4.0.tgz#782a3b3b4873388f93d087cd2971162f24eb6f2d"
-
node-int64@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b"
@@ -4505,6 +4536,10 @@ reduce-function-call@^1.0.1:
dependencies:
balanced-match "^0.4.2"
+redux-saga@^0.15.0:
+ version "0.15.0"
+ resolved "https://registry.yarnpkg.com/redux-saga/-/redux-saga-0.15.0.tgz#14c4aae5bfece7266d6b16e9050556a102fc6d38"
+
redux@^3.6.0:
version "3.6.0"
resolved "https://registry.yarnpkg.com/redux/-/redux-3.6.0.tgz#887c2b3d0b9bd86eca2be70571c27654c19e188d"
@@ -4696,6 +4731,10 @@ rx-lite@^3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-3.1.2.tgz#19ce502ca572665f3b647b10939f97fd1615f102"
+rx@2.3.24:
+ version "2.3.24"
+ resolved "https://registry.yarnpkg.com/rx/-/rx-2.3.24.tgz#14f950a4217d7e35daa71bbcbe58eff68ea4b2b7"
+
safe-buffer@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.0.1.tgz#d263ca54696cd8a306b5ca6551e92de57918fbe7"
@@ -4862,6 +4901,10 @@ source-map@~0.2.0:
dependencies:
amdefine ">=0.0.4"
+spawn-command@^0.0.2-1:
+ version "0.0.2"
+ resolved "https://registry.yarnpkg.com/spawn-command/-/spawn-command-0.0.2.tgz#9544e1a43ca045f8531aac1a48cb29bdae62338e"
+
spdx-correct@~1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-1.0.2.tgz#4b3073d933ff51f3912f03ac5519498a4150db40"
@@ -4963,6 +5006,12 @@ strip-ansi@3.0.1, strip-ansi@^3.0.0, strip-ansi@^3.0.1:
dependencies:
ansi-regex "^2.0.0"
+strip-ansi@^0.3.0:
+ version "0.3.0"
+ resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-0.3.0.tgz#25f48ea22ca79187f3174a4db8759347bb126220"
+ dependencies:
+ ansi-regex "^0.2.1"
+
strip-bom@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e"
@@ -4987,6 +5036,10 @@ style-loader@0.13.1:
dependencies:
loader-utils "^0.2.7"
+supports-color@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-0.2.0.tgz#d92de2694eb3f67323973d7ae3d8b55b4c22190a"
+
supports-color@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
@@ -5107,6 +5160,10 @@ tr46@~0.0.3:
version "0.0.3"
resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
+tree-kill@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.1.0.tgz#c963dcf03722892ec59cba569e940b71954d1729"
+
trim-right@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003"