1 | # Request Layer
|
2 |
|
3 | This is the request layer used here at One Day Only to handle request tracking between clients and server, as well as optimistic updates.
|
4 |
|
5 | ## The general idea:
|
6 |
|
7 | We do not directly make api requests from our clients but rather we pass actions via a socket server, which will later respond with the data we need. This is great, but due to its very
|
8 | asynchronous nature, its hard to keep track of requests. What this layer does is wrap each action with some meta information including a `requestId` and a `requestName` and then
|
9 | stores each request in state along with its current status (`pending`, `complete`, `failed`).
|
10 |
|
11 | ## How to use
|
12 |
|
13 | We need to target 4 areas of the system:
|
14 |
|
15 | + Redux middleware - to intercept wrapped actions. sure we could make thunk actions, but its easier to understand whats going on when the action system is sync.
|
16 | + Action creators - to wrap an action with the required meta data.
|
17 | + Reducers - to handle state mutations and provide optimistic updates.
|
18 | + Socket Server - to interpret the clientAction and properly format a responseAction.
|
19 |
|
20 | ```javascript
|
21 | /* Middleware */
|
22 | import { requestMiddleware } from 'request-layer'
|
23 | import io from 'socket.io-client'
|
24 |
|
25 | const socket = io("http:// ...")
|
26 | const store = createStore(
|
27 | applyMiddleware(requestMiddleware(socket))
|
28 | )
|
29 | ```
|
30 |
|
31 | ```javascript
|
32 | /* Action Creators */
|
33 | import { serverAction } from 'request-layer'
|
34 |
|
35 | export const someAction = (payload: Object, getRequestId: Function) => serverAction({
|
36 | type: String,
|
37 | payload,
|
38 | meta: {
|
39 | optimistic: Boolean,
|
40 | requestName: String
|
41 | }
|
42 | }, getRequestId)
|
43 | ```
|
44 |
|
45 | The reducer provided is actually a _reducer enhancer_ meaning it will wrap your reducers and quietly change state. The reducerEnhancer will also enhance your store with a
|
46 | redux-optimist reducer
|
47 | ```javascript
|
48 | /* Reducer */
|
49 | import { reducerEnhancer } from 'request-layer'
|
50 |
|
51 | const roodReducer = combineReducers({ ... })
|
52 | export default reducerEnhancer(roodReducer)
|
53 | ```
|
54 |
|
55 | ```javascript
|
56 | /* Socket Server */
|
57 | import { serverMiddleware } from 'request-layer'
|
58 |
|
59 | type Action = {
|
60 | type: string,
|
61 | payload: ?any,
|
62 | meta: ?Object
|
63 | }
|
64 |
|
65 | type HandlerParams = {
|
66 | action: ServerAction,
|
67 | resolve: (res: ResponseAction) => RequestAction,
|
68 | reject: (e: any) => RequestAction,
|
69 | socket: Object
|
70 | }
|
71 |
|
72 | const actionHandler = (params: HandlerParams) => {}
|
73 |
|
74 | serverMiddleware(actionHandler, socket)
|
75 | ```
|
76 |
|
77 | Additionally, request-layer provides a react `HOC` that passes a collection of tools into a components props. These tools can be used to get the status of requests and handle
|
78 | complete/failed requests.
|
79 | ```javascript
|
80 | import React, { Component } from 'react'
|
81 | import { requestTools, serverAction } from 'request-layer'
|
82 | import { connect } from 'react-redux'
|
83 | import _ from 'lodash'
|
84 |
|
85 | const enhance = _.flowRight(
|
86 | connect(state => ({requests: state.requests})),
|
87 | requestTools
|
88 | )
|
89 | export default
|
90 | enhance(class App extends Component {
|
91 |
|
92 | addItem = item => {
|
93 | store.dispatch(serverAction({
|
94 | type: "ADD_ITEMS",
|
95 | payload: item,
|
96 | meta: {
|
97 | requestName: "items.add"
|
98 | }
|
99 | }, id => {
|
100 | this.props.trace(id)
|
101 | .then(() => {
|
102 | // request is complete
|
103 | })
|
104 | .catch(e => {
|
105 | // request failed with error: e
|
106 | })
|
107 | }))
|
108 | }
|
109 |
|
110 | render() {
|
111 | return this.props.pending("items.fetch",
|
112 | <Loader></Loader>,
|
113 |
|
114 | <div>
|
115 | <Button disabled={this.props.pending("items.add")} onClick={this.addItem({name: "new item"})}>Add Item</Button>
|
116 |
|
117 | {this.props.failed("items.add", "failed to add item")}
|
118 | {this.props.complete("items.add", "successfully added item")}
|
119 | </div>
|
120 | )
|
121 | }
|
122 | })
|
123 | ``` |
\ | No newline at end of file |