The new Fetch API is way better than XHR to work with for sure, but theres still a decent way to go to make it dead simple, I've attempted to bridge that gap with this library :).
I have also added the ability to bind functions to be run when API responses are received. Functions can be bound to error responses, to success responses, and to all responses (both success and error). See methods for this below 'Making Requests' section.
Making Requests
To make requests you can use the standard 'get', 'put', 'post', 'del', and 'patch' methods. If you're server-side you need to set the host because node-fetch can't determine the base route
'Hello World' Example
; // absolute routes are needed server-side until Node.js implements native fetch,// you can set the base URL for server-side via the method below (host, port), or with 'process.env.BASE_URL'// this "base URL" will be prepended to all requestssimpleIsoFetch; // example usage for get request to 'http://locahost:3000'simpleIsoFetch; // identical to the above, convenience default for when no body/customization is needed (just uses string passed as route)simpleIsoFetch;
A More Thorough Example
// set to your app's hostname + port, (if hostname not provided, defaults to localhost, if hostname provided without port, 80 is assumed, if neither hostname nor port provided, http://localhost: + (process.env.PORT || 3000) used, function returns resulting base URL (note this is a static method, on class itself not instance)simpleIsoFetch; // normal usageconst aJsonObject = prop: 'example' const exampleParam = 'paramparamparam'; // the below will make a POST request to:// 'http://localhost:3000/api/paramparamparam?prop=valvalval&prop2=anotherVal'simpleIsoFetch // console.logs whatever the response is; // console.logs whatever the error is // there is flexibility built in to allow you to provide the route as the first argument and additional options as the second// the below will make a PUT request to:// 'http://localhost:3000/api/paramparamparam?prop=valvalval&prop2=anotherVal'simpleIsoFetch // console.logs whatever the response is; // console.logs whatever the error is // the below will make a DELETE request to:// 'http://localhost:3000/api/paramparamparam?prop=valvalval&prop2=anotherVal'// (note that DELETE and GET requests can't have a 'body' property per W3C spec)simpleIsoFetch // console.logs whatever the response is; // console.logs whatever the error is // full configurable options exposed below//// dummy bodyconst blogPost = title: 'Hey Guys' body: 'I\'m o simple!' //// dummy paramsconst id = '1234';const location = 'place'; // the below will make a POST request to:// 'http://localhost:3000/api/posts/1234/place/?anAnalyticsThing={"aDeeplyNestedProperty":"example"}&anotherProperty=example2'simpleIsoFetch // console.logs whatever the response is; // console.logs whatever the error is
Binding/Unbinding Functions to Responses
; // set host to your app's hostname for server-side fetchingsimpleIsoFetch; // bind function to error response, returns function to stop binding this function (useful for React's ComponentWillUnmount)const unbindThisErrorFunction = simpleIsoFetch; // unbinds the function that was bound above, so it will no longer get run upon error responsesconst wasBound = ; // the unbinding function returns 'true' if the function it tried to unbind was actually bound when it was called and 'false' if it was notconsole; // bind function to success response, returns function to unbind this function (useful for React's ComponentWillUnmount)const unbindThisSuccessFunction = simpleIsoFetch; // unbinds the function that was bound above, so it will no longer get run upon success responsesconst wasBound = ; // the unbinding function returns 'true' if the function it tried to unbind was actually bound when it was called and 'false' if it was notconsole; // bind function to all responses (success and error), returns function to unbind this function (useful for React's ComponentWillUnmount)const unbindThisResponseFunction = simpleIsoFetch // unbinds the function that was bound above, so it will no longer get run upon responsesconst wasBound = ; // the unbinding function returns 'true' if the function it tried to unbind was actually bound when it was called and 'false' if it was notconsole; // you can reference the arrays of bound functions with the below properties, note that if you modify these arrays directly and affect order or overwrite functions, your unbind functions will no longer worksimpleIsoFetchboundToError: // array of functions to be called upon errorsimpleIsoFetchboundToSuccess: // array of functions to be called upon success responsessimpleIsoFetchboundToResponse: // array of functions to be called upon all responses
Isomorphic (Universal) Redux use
Not far into making this library I had to solve the problem of being able to pass an instance of "SimpleIsoFetch" throughout my redux application in order to put persist the authentication cookie for server side requests in a universal app.
I provided a solution for this with using redux middleware and also provided a way to put the functions you have bound to API responses on your redux state and modify them with actions.
Note that if you are not using redux or not making a universal app that has authentication, you can still use everything above this point and have a nifty fetching tool, but if you do need to handle the isomorphic redux thing, you're covered below :).
The key point is that you can use simpleIsoFetch as a class and make an instance of it where you pass in an express "request" object, this will bind the cookie contained in that request to all uses of that instance. This instance can then be passed throughout your application to your redux action creators.
// on your root universal routeapp;
Use with redux-thunk
Since redux-thunk is very common for handling async requests with redux I have included middleware for this pattern, you can of course feel free to make your own sand just pass your 'simpleIsoFetchInstance' into that.
// in 'configureStore' file/function;;; { const finalCreateStore = createStore; const store = ; return store} // Your async action creators will now be curried with 'simpleIsoFetch' preceding 'dispatch', see example async action creator below... { return simpleIsoFetch
Adding API response function bindings to Redux state
In order to still have functions bound to API responses on our instance and have those carried through our isomorphic app we need to place the arrays of bound functions ('boundToError', 'boundToSuccess', and 'boundToResponse') on our redux state and make them modifiable with actions, here's how we do it
Attach SimpleIsoFetch to Redux root Reducer
/// in root reducer file;.../// assuming you're using 'combineReducers' ... simpleIsoFetch: bindingsReducer // simpleIsoFetch is expected name, can be modified
Initialization on Redux state
; // create simpleIsoFetch instanceconst simpleIsoFetch = ; // configure storeconst store = ; // feed store and instance into 'syncBindingsWithStore' function to place 'boundToError', 'boundToSuccess', and 'boundToResponse' arrays on state;
Binding functions to API responses
Here is an example of how to send a 'react-toastr' (http://tomchentw.github.io/react-toastr/) message upon error responses with a status code of 500 or greater.
(to actually get react-toastr fully working requires stylesheets as well, see a fully working example implemented here, try logging in without creating a user.)
// react-toastr library needs;const ToastMessageAnim = ToastMessageanimation; // used to create actions to bind functions to API call responses; @ static propTypes = dispatch: PropTypesfunc children: PropTypesobjectisRequired { // function for creating toast errors upon responses with status of 500 or greater this { resstatus >= 500 && resbodyerrors || errorMessage: resbody; } // transform response thisprops; } { // this is needed to avoid binding twice on hot reloading (good in principal regardless) thisprops; } { return <div> <ToastContainer toastMessageFactory= <ToastMessageAnim ...props timeOut=6000/> ref='container' className='toast-top-right'/> thispropschildren </div> ; }