import React from 'react'; import invariant from 'tiny-invariant'; import isEqual from 'lodash.isequal'; import { useClient } from './client-hook'; import { Endpoint, Method } from './endpoint'; import { RequestAction, requestReducer, RequestState } from './reducer'; import { useRequestContext } from './request-context'; export type RequestConfig = Readonly<{ variables?: V; params?: P; headers?: Record; onComplete?: (data: R) => unknown; }> export type RequestHandlerConfig = Readonly< RequestConfig & { force?: boolean } > export type RequestHandler = (config?: RequestHandlerConfig) => Promise; export function useRequest, V = Record, P = void>( endpoint: Endpoint

, config?: RequestConfig, ): [RequestHandler, RequestState] { const [client] = useClient(); const { defaultHeaders } = useRequestContext(); const [state, dispatch] = React.useReducer, RequestAction>>( requestReducer, { data: null, loading: false, isCalled: false, } ); const variableParam = requestVariableParamByMethod(endpoint.method); const handler = React.useCallback( (handlerConfig?: RequestHandlerConfig) => { if (state?.loading) { return Promise.resolve(null); } let params: P | undefined; let endpointUrl: string; let isSameRequest = true; if (typeof endpoint.url === 'function') { params = handlerConfig?.params ?? config?.params; invariant(params, 'Endpoind required params'); endpointUrl = endpoint.url(params); isSameRequest = !!state?.prevParams && isEqual(state.prevParams, params); } else { endpointUrl = endpoint.url; } const variables = { ...config?.variables, ...handlerConfig?.variables, }; const headers = { ...defaultHeaders, ...endpoint.headers, ...config?.headers, ...handlerConfig?.headers, }; if ( isSameRequest && state?.prevVariables && isEqual(state.prevVariables, variables) && state?.prevHeaders && isEqual(state.prevHeaders, headers) && !handlerConfig?.force ) { return Promise.resolve(state.data); } const onComplete = config?.onComplete ?? handlerConfig?.onComplete; dispatch({ type: 'call', headers, variables, params }); return client .request({ ...endpoint, url: endpointUrl, headers, [variableParam]: variables, }) .then( (response) => { dispatch({ type: 'success', response }); if (typeof onComplete === 'function') { onComplete(response.data); } return response.data; }, (response) => { dispatch({ type: 'failure', response }); throw response; } ); }, [state, config, client, endpoint, variableParam, defaultHeaders] ); return [ handler, state, ]; } function requestVariableParamByMethod(method: Method) { return [Method.GET, Method.DELETE].includes(method) ? 'params' : 'data'; }