Skip to content

Instantly share code, notes, and snippets.

@joshuaalpuerto
Last active March 27, 2020 10:44
Show Gist options
  • Save joshuaalpuerto/361272bd0f57cd8330c7ac141a8701e1 to your computer and use it in GitHub Desktop.
Save joshuaalpuerto/361272bd0f57cd8330c7ac141a8701e1 to your computer and use it in GitHub Desktop.
Hooks for requesting api built-in state
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