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"