Last active
January 16, 2021 08:07
-
-
Save l0gicgate/9f4d05fbc3b19d2e795fc1c59bafd435 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as Rx from 'rxjs'; | |
import queryString from 'query-string'; | |
/** | |
* This function simply transforms any actions into an array of actions | |
* This enables us to use the synthax Observable.of(...actions) | |
* If an array is passed to this function it will be returned automatically instead | |
* Example: mapObservables({ type: ACTION_1 }) -> will return: [{ type: ACTION_1 }] | |
* Example2: mapObservables([{ type: ACTION_1 }, { type: ACTION_2 }]) -> will return: [{ type: ACTION_1 }, { type: ACTION_2 }] | |
*/ | |
function mapObservables(observables) { | |
if (observables === null) { | |
return null; | |
} else if (Array.isArray(observables)) { | |
return observables; | |
} | |
return [observables]; | |
} | |
/** | |
* Possible Options: | |
* params (optional): Object of parameters to be appended to query string of the uri e.g: { foo: bar } (Used with GET requests) | |
* headers (optional): Object of headers to be appended to the request headers | |
* data (optional): Any type of data you want to be passed to the body of the request (Used for POST, PUT, PATCH, DELETE requests) | |
* uri (required): Uri to be appended to our API base url | |
*/ | |
function makeRequest(method, options) { | |
let uri = options.uri; | |
if (method === 'get' && options.params) { | |
uri += `?${queryString.stringify(options.params)}`; | |
} | |
return Rx.Observable.ajax({ | |
headers: { | |
'Content-Type': 'application/json;charset=utf-8', | |
...options.headers, | |
}, | |
responseType: 'json', | |
timeout: 60000, | |
body: options.data || null, | |
method, | |
url: `http://www.website.com/api/v1/${uri}`, | |
// Most often you have a fixed API url so we just append a URI here to our fixed URL instead of repeating the API URL everywhere. | |
}) | |
.flatMap(({ response }) => { | |
/** | |
* Here we handle our success callback, anyt actions returned from it will be dispatched. | |
* You can return a single action or an array of actions to be dispatched eg. [{ type: ACTION_1 }, { type: ACTION_2 }]. | |
*/ | |
if (options.onSuccess) { | |
const observables = mapObservables(options.onSuccess(response)); | |
if (observables) { | |
// This is only being called if our onSuccess callback returns any actions in which case we have to dispatch them | |
return Rx.Observable.of(...observables); | |
} | |
} | |
return Rx.Observable.of(); | |
}) | |
.catch((error) => { | |
/** | |
* This if case is to handle non-XHR errors gracefully that may be coming from elsewhere in our application when we fire | |
* an Observable.ajax request | |
*/ | |
if (!error.xhr) { | |
if (options.onError) { | |
const observables = mapObservables(options.onError(null)); // Note we pass null to our onError callback because it's not an XHR error | |
if (observables) { | |
// This is only being called if our onError callback returns any actions in which case we have to dispatch them | |
return Rx.Observable.of(...observables); | |
} | |
} | |
// You always have to ensure that you return an Observable, even if it's empty from all your Observables. | |
return Rx.Observable.of(); | |
} | |
const { xhr } = error; | |
const { response } = error.xhr; | |
const actions = []; | |
const resArg = response || null; | |
let message = null; | |
if (xhr.status === 0) { | |
message = 'Server is not responding.'; | |
} else if (xhr.status === 401) { | |
// For instance we handle a 401 here, if you use react-router-redux you can simply push actions here to your router | |
actions.push( | |
replace('/login'), | |
); | |
} else if ( | |
response | |
&& response.errorMessage | |
) { | |
/* | |
* In this case the errorMessage parameter would refer to SampleApiResponse.json 400 example | |
* { "errorMessage": "Invalid parameter." } | |
*/ | |
message = response.errorMessage; | |
} | |
if (options.onError) { | |
// Here if our options contain an onError callback we can map the returned Actions and push them into our action payload | |
mapObservables(options.onError(resArg)).forEach(o => actions.push(o)); | |
} | |
if (message) { | |
actions.push(showMessageAction(message)); | |
} | |
/** | |
* You can return multiple actions in one observable by adding arguments Rx.Observable.of(action1, action2, ...) | |
* The actions always have to have a type { type: 'ACTION_1' } | |
*/ | |
return Rx.Observable.of(...actions); | |
}); | |
} | |
const API = { | |
get: options => makeRequest('get', options), | |
post: options => makeRequest('post', options), | |
put: options => makeRequest('put', options), | |
patch: options => makeRequest('patch', options), | |
delete: options => makeRequest('delete', options), | |
}; | |
export default API; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import API from 'API'; | |
const FETCH_PROFILE = 'FETCH_PROFILE'; | |
const FETCH_PROFILE_SUCCESS = 'FETCH_PROFILE_SUCCESS'; | |
const FETCH_PROFILE_ERROR = 'FETCH_PROFILE_ERROR'; | |
const FETCH_OTHER_THING = 'FETCH_OTHER_THING'; | |
const FETCH_OTHER_THING_SUCCESS = 'FETCH_OTHER_THING_SUCCESS'; | |
const FETCH_OTHER_THING_ERROR = 'FETCH_OTHER_THING_ERROR'; | |
function fetchProfile(id) { | |
return { | |
type: FETCH_PROFILE, | |
id, | |
}; | |
} | |
function fetchProfileSuccess(data) { | |
return { | |
type: FETCH_PROFILE_SUCCESS, | |
data, | |
}; | |
} | |
function fetchProfileError(error) { | |
return { | |
type: FETCH_PROFILE_ERROR, | |
error, | |
}; | |
} | |
function fetchOtherThing(id) { | |
return { | |
type: FETCH_OTHER_THING, | |
id, | |
}; | |
} | |
const fetchProfileEpic = action$ => action$. | |
ofType(FETCH_PROFILE) | |
.switchMap(({ id }) => API.get({ | |
uri: 'profile', | |
params: { | |
id, | |
}, | |
/* | |
* We could also dispatch multiple actions using an array here you could dispatch another API request if needed | |
* We can redispatch another action to fire another epic if we want also. | |
* In both onSuccess and onError you can return a single action, an array of actions or null | |
* Note here we fire fetchOtherThing(data.someOtherThingId) which will trigger our fetchOtherThingEpic! | |
* In this case the data parameter would refer to SampleApiResponse.json 200 example | |
* { firstName: "John", lastName: "Doe" } | |
*/ | |
onSuccess: ({ data }) => [fetchProfileSuccess(data), fetchOtherThing(data.someOtherThingId)], | |
onError: error => fetchProfileError(error), | |
}) | |
const fetchOtherThingEpic = action$ => action$. | |
ofType(FETCH_OTHER_THING) | |
.switchMap(({ id }) => API.get({ | |
uri: 'other-thing', | |
params: { | |
id, | |
}, | |
onSuccess: ... | |
onError: ... | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* If possible, you should standardize your API response which will make error/data handling a lot easier on the client side | |
* Note, this data format is to work with the example code above | |
*/ | |
/** | |
* Status code: 401 | |
* This error would be caught in the .catch() method of our API wrapper | |
*/ | |
{ | |
"errorMessage": "Please login to perform this operation", | |
"data": null, | |
} | |
/** | |
* Status code: 400 | |
* This error would be caught in the .catch() method of our API wrapper | |
* | |
*/ | |
{ | |
"errorMessage": "Invalid parameter.", | |
"data": null | |
} | |
/** | |
* Status code: 200 | |
* This would be passed to our onSuccess function specified in our API options | |
*/ | |
{ | |
"errorMessage": 'Please login to perform this operation', | |
"data": { | |
"firstName": "John", | |
"lastName": "Doe" | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment