Last active
March 27, 2020 10:44
-
-
Save joshuaalpuerto/361272bd0f57cd8330c7ac141a8701e1 to your computer and use it in GitHub Desktop.
Hooks for requesting api built-in state
This file contains hidden or 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 { useReducer, useCallback, useRef, useEffect } from 'react'; | |
import cond from 'lodash/cond'; | |
import includes from 'lodash/fp/includes'; | |
import stubTrue from 'lodash/stubTrue'; | |
const initialState = { | |
response: null, // could be whatever type of response they are ,expecting | |
loading: false, | |
success: false, | |
error: false, | |
}; | |
function apiReducer(state = initialState, action = {}) { | |
switch (action.type) { | |
case 'FETCHING_API': | |
return { | |
...state, | |
loading: true, | |
success: false, | |
error: false, | |
}; | |
case 'SUCCESS_API': | |
return { | |
...state, | |
response: action.payload, | |
loading: false, | |
success: true, | |
error: false, | |
}; | |
case 'ERROR_API': | |
return { | |
...state, | |
response: null, | |
loading: false, | |
success: false, | |
error: action.payload, | |
}; | |
default: | |
return state; | |
} | |
} | |
/** | |
* Be careful on setting `{}` this is not shallow copy and if | |
* passed as 2nd argument, it will lose equality and will trigger | |
* re-render | |
*/ | |
const useApiFetcher = props => { | |
const [state, dispatch] = useReducer(apiReducer, initialState); | |
const isMounted = useRef(true); | |
useEffect(() => { | |
return () => (isMounted.current = false); | |
}, []); | |
const cancellableDispatch = useCallback( | |
props => { | |
if (isMounted.current) { | |
dispatch(props); | |
} | |
}, | |
[isMounted] | |
); | |
const makeRequest = useCallback( | |
(url, options) => { | |
return (async () => { | |
// dispatch only component is mounted | |
cancellableDispatch({ type: 'FETCHING_API' }); | |
try { | |
const result = await request(url, options); | |
cancellableDispatch({ type: 'SUCCESS_API', payload: result }); | |
} catch (err) { | |
cancellableDispatch({ type: 'ERROR_API', payload: err }); | |
} | |
})(); | |
}, | |
[cancellableDispatch] | |
); | |
return [state, makeRequest]; | |
}; | |
/** | |
* Have to wrap correctly for fetch to throw errors correclty | |
* @param {*} url | |
* @param {*} options | |
*/ | |
export function request(url, options) { | |
return fetch(url, options) | |
.then(checkStatus) | |
.then(cleanStatus) | |
.then(parseResponse); | |
} | |
/** | |
* Parses the JSON returned by a network request | |
*/ | |
function cleanStatus(response) { | |
if (response.status === 204 || response.status === 205) { | |
return null; | |
} | |
return response; | |
} | |
function parseResponse(response) { | |
const contentType = response.headers.get('content-type'); | |
const parseBlob = () => response.blob(); | |
const parseJson = () => response.json(); | |
return cond([ | |
[includes('application/vnd.openxml'), parseBlob], | |
[stubTrue, parseJson], | |
])(contentType); | |
} | |
/** | |
* Checks if a network request came back fine, and throws an error if not | |
*/ | |
function checkStatus(response) { | |
if (response.status >= 200 && response.status < 300) { | |
return response; | |
} | |
const error = new Error(response.statusText); | |
error.response = response; | |
throw error; | |
} | |
export default useApiFetcher; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment