Browse Source

working exemple with router and fetch wrapper

Skylsmoi 6 years ago
parent
commit
2f49737453

+ 2 - 3
dist/index.html View File

@@ -5,9 +5,8 @@
5 5
   <title>Tracim</title>
6 6
   <link rel='shortcut icon' href='favicon.ico'>
7 7
 
8
-  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous">
9
-
10
-  <!--<link rel='stylesheet' href='asset/font-awesome-4.7.0/css/font-awesome.min.css'>-->
8
+  <!--link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous"-->
9
+  <!--link rel='stylesheet' href='asset/font-awesome-4.7.0/css/font-awesome.min.css'-->
11 10
 </head>
12 11
 <body>
13 12
 <div id='content'></div>

+ 30 - 0
jsonserver/server.js View File

@@ -0,0 +1,30 @@
1
+const jsonServer = require('json-server')
2
+const jsonDb = require('./static_db.json')
3
+const server = jsonServer.create()
4
+const router = jsonServer.router() // for persistance : jsonServer.router('static_db.json')
5
+const middlewares = jsonServer.defaults()
6
+const GLOBAL_PORT = 3001
7
+
8
+server.use(middlewares)
9
+server.use(jsonServer.bodyParser)
10
+
11
+// res.jsonp(req.query)
12
+server.get('/echo', (req, res) => res.jsonp('gg'))
13
+server.get('/login', (req, res) => res.jsonp(jsonDb.login))
14
+server.post('/user/login', (req, res) => {
15
+  if (req.body.login !== '' && req.body.password !== '') return res.jsonp(jsonDb.user_logged)
16
+  else return res.jsonp('error')
17
+})
18
+server.get('/user_logged', (req, res) => res.jsonp(jsonDb.user_logged))
19
+server.delete('/deletenodata', (req,res) => res.status(204).jsonp(''))
20
+
21
+// server.put('/api/data/raw_materials_vendors/:vendorid', (req, res) => {
22
+//  res.jsonp(jsonVendorColorData.vendorVariableData)
23
+//   console.log(req.body)
24
+//   res.jsonp('gg')
25
+// })
26
+
27
+server.use(router)
28
+server.listen(GLOBAL_PORT, () => {
29
+  console.log('JSON Server is running on port : ' + GLOBAL_PORT)
30
+})

+ 10 - 0
jsonserver/static_db.json View File

@@ -0,0 +1,10 @@
1
+{
2
+  "login": true,
3
+  "user_logged": {
4
+    "id": 5,
5
+    "username": "Smoi",
6
+    "firstname": "Côme",
7
+    "lastname": "Stoilenom",
8
+    "email": "osef@algoo.fr"
9
+  }
10
+}

+ 5 - 1
package.json View File

@@ -4,6 +4,7 @@
4 4
   "description": "",
5 5
   "main": "index.js",
6 6
   "scripts": {
7
+    "mockapi": "node jsonserver/server.js",
7 8
     "servdev": "NODE_ENV=development webpack-dev-server --watch --colors --inline --hot --progress",
8 9
     "build": "NODE_ENV=production webpack -p",
9 10
     "test": "echo \"Error: no test specified\" && exit 1"
@@ -36,9 +37,12 @@
36 37
     "stylus": "^0.54.5",
37 38
     "stylus-loader": "^3.0.1",
38 39
     "url-loader": "^0.6.2",
39
-    "webpack": "^3.8.1"
40
+    "webpack": "^3.8.1",
41
+    "whatwg-fetch": "^2.0.3"
40 42
   },
41 43
   "devDependencies": {
44
+    "json-server": "^0.12.0",
45
+    "react-router-dom": "^4.2.2",
42 46
     "webpack-dev-server": "^2.9.2"
43 47
   },
44 48
   "standard": {

+ 85 - 0
src/action-creator.async.js View File

@@ -0,0 +1,85 @@
1
+import { FETCH_CONFIG } from './helper.js'
2
+import {
3
+  USER_LOGIN,
4
+  USER_CONNECTED,
5
+  updateUserConnected
6
+} from './action-creator.sync.js'
7
+
8
+/*
9
+ * fetchWrapper(obj)
10
+ *
11
+ * Params:
12
+ *   An Object with the following attributes :
13
+ *     url - string - url of the end point to call
14
+ *     param - object - param to send with fetch call (eg. header)
15
+ *       param.method - string - REQUIRED - method of the http call
16
+ *     actionName - string - name of the action to dispatch with 'PENDING' and 'SUCCESS' respectively before and after the http request
17
+ *     dispatch - func - redux dispatcher function
18
+ *
19
+ * Returns:
20
+ *   An object Response generated by whatwg-fetch with a new property 'json' containing the data received or informations in case of failure
21
+ *
22
+ * This function create a http async request using whatwg-fetch while dispatching a PENDING and a SUCCESS redux action.
23
+ * It also adds, to the Response of the fetch request, the json value so that the redux action have access to the status and the data
24
+ */
25
+const fetchWrapper = async ({url, param, actionName, dispatch, debug = false}) => {
26
+  dispatch({type: `${param.method}/${actionName}/PENDING`})
27
+
28
+  const fetchResult = await fetch(url, param)
29
+  fetchResult.json = await (async () => {
30
+    switch (fetchResult.status) {
31
+      case 200:
32
+      case 304:
33
+        return fetchResult.json()
34
+      case 204:
35
+        return ''
36
+      case 400:
37
+      case 404:
38
+        return ''
39
+      case 409:
40
+        return ''
41
+      case 500:
42
+      case 501:
43
+      case 502:
44
+      case 503:
45
+      case 504:
46
+        const jsonRes = await fetchResult.json()
47
+        return jsonRes.errorHandled ? jsonRes.errorMsg : ''
48
+    }
49
+  })()
50
+  if (debug) console.log(`fetch ${param.method}/${actionName} result: `, fetchResult)
51
+
52
+  dispatch({type: `${param.method}/${actionName}/SUCCESS`, data: fetchResult.json})
53
+
54
+  return fetchResult
55
+}
56
+
57
+export const userLogin = (login, password) => async dispatch => {
58
+  const fetchUserLogin = await fetchWrapper({
59
+    url: 'http://localhost:3001/user/login',
60
+    param: {...FETCH_CONFIG, method: 'POST', body: JSON.stringify({login, password})},
61
+    actionName: USER_LOGIN,
62
+    dispatch
63
+  })
64
+  if (fetchUserLogin.status === 200) dispatch(updateUserConnected(fetchUserLogin.json))
65
+}
66
+
67
+export const getUserConnected = () => async dispatch => {
68
+  const fetchUserLogged = await fetchWrapper({
69
+    url: 'http://localhost:3001/user_logged',
70
+    param: {...FETCH_CONFIG, method: 'GET'},
71
+    actionName: USER_CONNECTED,
72
+    dispatch
73
+  })
74
+  if (fetchUserLogged.status === 200) dispatch(updateUserConnected(fetchUserLogged.json))
75
+}
76
+
77
+// export const testResponseNoData = () => async dispatch => {
78
+//   const fetchResponseNoData = await fetchWrapper({
79
+//     url: 'http://localhost:3001/deletenodata',
80
+//     param: {...FETCH_CONFIG, method: 'DELETE'},
81
+//     actionName: 'TestNoData',
82
+//     dispatch
83
+//   })
84
+//   console.log('jsonResponseNoData', fetchResponseNoData)
85
+// }

+ 4 - 0
src/action-creator.sync.js View File

@@ -0,0 +1,4 @@
1
+export const USER_LOGIN = 'User/Login'
2
+
3
+export const USER_CONNECTED = 'User/Connected'
4
+export const updateUserConnected = user => ({ type: `Update/${USER_CONNECTED}`, user })

+ 11 - 0
src/component/ConnectionForm.jsx View File

@@ -0,0 +1,11 @@
1
+import React from 'react'
2
+
3
+export const Connection = props => {
4
+  return (
5
+    <div>
6
+      <input type='text' onChange={props.onChangeLogin} placeholder='Login' />
7
+      <input type='text' onChange={props.onChangePassword} placeholder='Password' />
8
+      <button type='button' onClick={props.onClickSubmit}>Connect</button>
9
+    </div>
10
+  )
11
+}

+ 9 - 0
src/component/Footer.jsx View File

@@ -0,0 +1,9 @@
1
+import React from 'react'
2
+
3
+export const Footer = props => {
4
+  return (
5
+    <div>
6
+      Footer
7
+    </div>
8
+  )
9
+}

+ 12 - 0
src/component/Header.jsx View File

@@ -0,0 +1,12 @@
1
+import React from 'react'
2
+
3
+export const Header = props => {
4
+  return (
5
+    <div>
6
+      { props.user.isLogedIn
7
+        ? `'Soir ${props.user.firstname} ${props.user.lastname}.`
8
+        : 'Why dont you connect yourself ?'
9
+      }
10
+    </div>
11
+  )
12
+}

+ 39 - 0
src/container/Login.jsx View File

@@ -0,0 +1,39 @@
1
+import React from 'react'
2
+import { connect } from 'react-redux'
3
+import { Header } from '../component/Header.jsx'
4
+import { ConnectionForm } from '../component/ConnectionForm.jsx'
5
+import { Footer } from '../component/Footer.jsx'
6
+import { userLogin } from '../action-creator.async.js'
7
+
8
+class Login extends React.Component {
9
+  constructor (props) {
10
+    super(props)
11
+    this.state = {
12
+      inputLogin: '',
13
+      inputPassword: ''
14
+    }
15
+  }
16
+
17
+  handleChangeLogin = e => this.setState({inputLogin: e.target.value})
18
+  handleChangePassword = e => this.setState({inputPassword: e.target.value})
19
+
20
+  handleClickSubmit = () => this.props.dispatch(userLogin(this.state.inputLogin, this.state.inputPassword))
21
+
22
+  render () {
23
+    const { user } = this.props
24
+    return (
25
+      <div>
26
+        <Header user={user} />
27
+        <ConnectionForm
28
+          onChangeLogin={this.handleChangeLogin}
29
+          onChangePassword={this.handleChangePassword}
30
+          onClickSubmit={this.handleClickSubmit}
31
+        />
32
+        <Footer />
33
+      </div>
34
+    )
35
+  }
36
+}
37
+
38
+const mapStateToProps = ({ user }) => ({ user })
39
+export default connect(mapStateToProps)(Login)

+ 20 - 0
src/container/Page.jsx View File

@@ -0,0 +1,20 @@
1
+import React from 'react'
2
+import { connect } from 'react-redux'
3
+import { Header } from '../component/Header.jsx'
4
+import { Footer } from '../component/Footer.jsx'
5
+
6
+class Page extends React.Component {
7
+  render () {
8
+    const { user } = this.props
9
+    return (
10
+      <div>
11
+        <Header user={user} />
12
+        <div>You are now connected, gg</div>
13
+        <Footer />
14
+      </div>
15
+    )
16
+  }
17
+}
18
+
19
+const mapStateToProps = ({ user }) => ({ user })
20
+export default connect(mapStateToProps)(Page)

+ 27 - 0
src/container/Tracim.jsx View File

@@ -0,0 +1,27 @@
1
+import React from 'react'
2
+import { connect } from 'react-redux'
3
+import Login from './Login.jsx'
4
+import Page from './Page.jsx'
5
+import {
6
+  BrowserRouter,
7
+  Route
8
+} from 'react-router-dom'
9
+
10
+class Tracim extends React.Component {
11
+  render () {
12
+    return (
13
+      <BrowserRouter>
14
+        <div>
15
+          <Route path='/' render={() =>
16
+            this.props.user.isLogedIn
17
+              ? <Page />
18
+              : <Login />
19
+          } />
20
+        </div>
21
+      </BrowserRouter>
22
+    )
23
+  }
24
+}
25
+
26
+const mapStateToProps = ({ user }) => ({ user })
27
+export default connect(mapStateToProps)(Tracim)

+ 6 - 0
src/helper.js View File

@@ -0,0 +1,6 @@
1
+export const FETCH_CONFIG = {
2
+  headers: {
3
+    'Accept': 'application/json',
4
+    'Content-Type': 'application/json'
5
+  }
6
+}

+ 2 - 7
src/index.js View File

@@ -2,18 +2,13 @@ import React from 'react'
2 2
 import ReactDOM from 'react-dom'
3 3
 import { Provider } from 'react-redux'
4 4
 import { store } from './store.js'
5
+import Tracim from './container/Tracim.jsx'
5 6
 
6 7
 // require('./css/index.styl')
7 8
 
8
-const Temp = class Temp extends React.Component {
9
-  render () {
10
-    return (<div>It Works</div>)
11
-  }
12
-}
13
-
14 9
 ReactDOM.render(
15 10
   <Provider store={store}>
16
-    <Temp />
11
+    <Tracim />
17 12
   </Provider>
18 13
   , document.getElementById('content')
19 14
 )

+ 0 - 6
src/reducer/init.js View File

@@ -1,6 +0,0 @@
1
-export default function init (state = {}, action) {
2
-  switch (action.type) {
3
-    default:
4
-      return state
5
-  }
6
-}

+ 2 - 2
src/reducer/root.js View File

@@ -1,6 +1,6 @@
1 1
 import { combineReducers } from 'redux'
2
-import init from './init.js'
2
+import user from './user.js'
3 3
 
4
-const rootReducer = combineReducers({ init })
4
+const rootReducer = combineReducers({ user })
5 5
 
6 6
 export default rootReducer

+ 15 - 0
src/reducer/user.js View File

@@ -0,0 +1,15 @@
1
+import { USER_CONNECTED } from '../action-creator.sync.js'
2
+
3
+export default function user (state = {
4
+  isLogedIn: false,
5
+  username: '',
6
+  email: ''
7
+}, action) {
8
+  switch (action.type) {
9
+    case `Update/${USER_CONNECTED}`:
10
+      return {...state, isLogedIn: true, ...action.user}
11
+
12
+    default:
13
+      return state
14
+  }
15
+}

+ 6 - 5
src/store.js View File

@@ -7,11 +7,12 @@ import rootReducer from './reducer/root.js'
7 7
 
8 8
 // const sagaMiddleware = createSagaMiddleware()
9 9
 
10
-export const store = (
11
-  (middleware, reduxDevTools) => createStore(rootReducer, compose(middleware, reduxDevTools || (f => f)))
12
-)(
13
-  applyMiddleware(thunkMiddleware, /* sagaMiddleware, */ createLogger()),
14
-  window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
10
+export const store = createStore(
11
+  rootReducer,
12
+  compose(
13
+    applyMiddleware(thunkMiddleware, /* sagaMiddleware, */ createLogger()),
14
+    (window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()) || (f => f)
15
+  )
15 16
 )
16 17
 
17 18
 // sagaMiddleware.run(rootSaga)

+ 5 - 5
webpack.config.js View File

@@ -4,7 +4,7 @@ const isProduction = process.env.NODE_ENV === 'production'
4 4
 
5 5
 module.exports = {
6 6
   entry: {
7
-    app: ['babel-polyfill', './src/index.js'],
7
+    app: ['babel-polyfill', 'whatwg-fetch', './src/index.js'],
8 8
     vendor: [
9 9
       'babel-plugin-transform-class-properties',
10 10
       'babel-plugin-transform-object-assign',
@@ -16,14 +16,14 @@ module.exports = {
16 16
       'react',
17 17
       'react-dom',
18 18
       'react-redux',
19
-      'react-router',
20
-      // 'react-router-dom',
19
+      // 'react-router',
20
+      'react-router-dom',
21 21
       // 'react-select',
22 22
       'redux',
23 23
       'redux-logger',
24 24
       // 'redux-saga',
25 25
       'redux-thunk',
26
-      // 'whatwg-fetch',
26
+      'whatwg-fetch',
27 27
       // 'classnames'
28 28
     ]
29 29
   },
@@ -68,7 +68,7 @@ module.exports = {
68 68
       use: ['style-loader', 'css-loader', 'stylus-loader']
69 69
     }, {
70 70
       test: /\.(jpg|png|svg)$/,
71
-      use: ['url-loader'],
71
+      loader: 'url-loader',
72 72
       options: {
73 73
         limit: 25000
74 74
       }