Skip to content

Commit 53a84f8

Browse files
committed
hot reload + api / production on 3000 to match CRA
1 parent 75b05cc commit 53a84f8

File tree

13 files changed

+224
-45
lines changed

13 files changed

+224
-45
lines changed

README.md

+8-3
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,18 @@ Install
77
-------
88
```bash
99
npm install
10-
npm run build
11-
npm run start:server
1210
```
1311

1412
### Development
15-
This just runs create react app, its great!
13+
This runs the creat react app with hot reloading containers + reducers
1614
```bash
1715
npm start
1816
```
1917

18+
### Production
19+
-------
20+
```bash
21+
npm run build
22+
npm run now-start
23+
```
24+

package.json

+7-3
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22
"name": "ssr-create-react-app-v2",
33
"version": "0.1.0",
44
"private": true,
5+
"proxy": "http://localhost:3001/",
56
"devDependencies": {
67
"babel-cli": "^6.24.1",
8+
"concurrently": "^3.4.0",
79
"react-scripts": "0.8.5"
810
},
911
"dependencies": {
@@ -16,12 +18,14 @@
1618
"react-dom": "^15.5.4",
1719
"react-redux": "^5.0.4",
1820
"react-router-dom": "^4.1.1",
19-
"redux": "^3.6.0"
21+
"redux": "^3.6.0",
22+
"redux-saga": "^0.15.0"
2023
},
2124
"scripts": {
22-
"start": "react-scripts start",
25+
"start": "./node_modules/.bin/concurrently \"npm run start:server\" \"npm run start:client\"",
26+
"start:client": "react-scripts start",
2327
"start:server": "NODE_ENV=development node server/index.js",
24-
"now-start": "NODE_ENV=production node server/index.js",
28+
"now-start": "NODE_ENV=production PORT=3000 node server/index.js",
2529
"build": "react-scripts build",
2630
"test": "react-scripts test --env=jsdom",
2731
"eject": "react-scripts eject"

server/routes/api.js

+10-1
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,14 @@ router.get('/', function(req, res, next) {
1010
res.json({})
1111
})
1212

13+
const users = {
14+
1: { email: '[email protected]' }
15+
}
16+
router.get('/users/:id', function(req, res, next) {
17+
if(Object.keys(users).indexOf(req.params.id) > -1) {
18+
res.json(users[req.params.id])
19+
} else {
20+
res.status(404).json({})
21+
}
22+
})
1323
module.exports = router
14-

src/actions/user.js

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
1-
import { SET, RESET } from '../types/user'
1+
import { REQUEST, SET, RESET } from '../types/user'
2+
3+
export function request(id) {
4+
return {
5+
type: REQUEST,
6+
id
7+
}
8+
}
29

310
export function set(payload){
411
return {
@@ -12,4 +19,3 @@ export function reset(){
1219
type: RESET
1320
}
1421
}
15-

src/api.js

+46-5
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@ class Api {
77
if (!options){
88
return
99
}
10-
const {token} = options
10+
const {token, prefix} = options
1111
this.token = token
12+
this.prefix = prefix
1213
}
1314
getJsonHeaders(){
1415
return {
@@ -34,11 +35,51 @@ class Api {
3435
_buildQueryString(data){
3536
return '?' + Object.keys(data).map(d=>d+'='+encodeURIComponent(data[d]))
3637
}
38+
parseJson(res) {
39+
return res.json()
40+
}
41+
checkStatus(res) {
42+
if (res.status >= 200 && res.status < 300) {
43+
return res
44+
} else {
45+
var error = new Error(res.statusText)
46+
error.response = res
47+
throw error
48+
}
49+
}
50+
get(fixture, id) {
51+
if(id) {
52+
return fetch(`${this.apiUrl}${this.prefix}/${fixture}/${id}`, this.getJsonHeaders())
53+
.then(this.checkStatus)
54+
.then(this.parseJson)
55+
.then(data => {
56+
return {ok: true, data}
57+
}).catch(err => {
58+
return {ok: false, err}
59+
})
60+
} else {
61+
return fetch(`${this.apiUrl}${this.prefix}/${fixture}`, this.getJsonHeaders())
62+
.then(this.checkStatus)
63+
.then(this.parseJson)
64+
.then(data => {
65+
return {ok: true, data}
66+
}).catch(err => {
67+
return {ok: false, err}
68+
})
69+
}
70+
}
3771
}
3872

39-
export class MainApi extends Api{
40-
constructor(options){
41-
super(options)
42-
this.prefix = '/api'
73+
const create = (prefix = '/api') => {
74+
const api = new Api({prefix})
75+
76+
const getUser = (id) => api.get('users', id)
77+
78+
return {
79+
getUser
4380
}
4481
}
82+
83+
export default {
84+
create
85+
}

src/config.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
let apiUrl = 'http://localhost:3001'
1+
let apiUrl = 'http://localhost:3000'
22
if (process.env.NODE_ENV === 'production') {
33
apiUrl = ''
44
}

src/containers/SecondPage.js

+3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ import { Link } from 'react-router-dom'
77
import './SecondPage.css'
88

99
class SecondPage extends Component {
10+
componentDidMount() {
11+
this.props.userActions.request(1)
12+
}
1013
render() {
1114
return (
1215
<div className='bold'>

src/index.js

+17-8
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,22 @@ import App from './containers/App'
1111
const initialState = {}
1212
const store = configureStore(initialState)
1313

14-
ReactDOM.render(
15-
<Provider store={store}>
16-
<BrowserRouter>
17-
<App />
18-
</BrowserRouter>
19-
</Provider>
20-
, document.getElementById('root')
21-
)
14+
const render = (Component) => {
15+
ReactDOM.render(
16+
<Provider store={store}>
17+
<BrowserRouter>
18+
<Component />
19+
</BrowserRouter>
20+
</Provider>,
21+
document.getElementById('root')
22+
)
23+
}
2224

25+
render(App)
2326

27+
if(process.env.NODE_ENV === 'development' && module.hot) {
28+
module.hot.accept('./containers/App', () => {
29+
const NewApp = require('./containers/App').default
30+
render(NewApp)
31+
})
32+
}

src/sagas/index.js

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { fork } from 'redux-saga/effects'
2+
import userSaga from './user'
3+
import API from '../api'
4+
5+
const api = API.create()
6+
7+
export default function * rootSaga() {
8+
yield [
9+
fork(userSaga(api))
10+
]
11+
}

src/sagas/user.js

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import * as userActions from '../actions/user'
2+
import * as userTypes from '../types/user'
3+
import { call, put, takeLatest } from 'redux-saga/effects'
4+
5+
function * getUser(api, {id}) {
6+
if(!id) {
7+
yield put(userActions.reset())
8+
} else {
9+
const response = yield call(api.getUser, id)
10+
11+
if(response.ok) {
12+
yield put(userActions.set(response.data))
13+
} else {
14+
yield put(userActions.reset())
15+
}
16+
}
17+
}
18+
19+
const userSaga = (api) => {
20+
return function * () {
21+
yield [
22+
takeLatest(userTypes.REQUEST, getUser, api)
23+
]
24+
}
25+
}
26+
27+
export default userSaga

src/store.js

+11-4
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
import { createStore, applyMiddleware, compose } from 'redux'
22
import reducers from './reducers'
3+
import rootSaga from './sagas'
34
//import createLogger from 'redux-logger'
4-
//import createSagaMiddleware from 'redux-saga'
5+
import createSagaMiddleware from 'redux-saga'
56

67
//const logger = createLogger()
7-
//const sagaMiddleware = createSagaMiddleware()
8+
const sagaMiddleware = createSagaMiddleware()
89

910
export default function configureStore(initialState = {}) {
1011
// Create the store with two middlewares
1112
const middlewares = [
12-
// sagaMiddleware
13+
sagaMiddleware
1314
//, logger
1415
]
1516

@@ -24,8 +25,14 @@ export default function configureStore(initialState = {}) {
2425
)
2526

2627
// Extensions
27-
//store.runSaga = sagaMiddleware.run
28+
sagaMiddleware.run(rootSaga)
2829
store.asyncReducers = {} // Async reducer registry
2930

31+
if(process.env.NODE_ENV === 'development' && module.hot) {
32+
module.hot.accept('./reducers', () => {
33+
store.replaceReducer(require('./reducers').default)
34+
})
35+
}
36+
3037
return store
3138
}

src/types/user.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
const prefix = 'USER/'
22

3+
export const REQUEST = prefix + 'REQUEST'
34
export const SET = prefix + 'SET'
45
export const RESET = prefix + 'RESET'
5-

0 commit comments

Comments
 (0)