Comparing version 0.1.82 to 0.1.83
@@ -1,2 +0,2 @@ | ||
import { HTTPMethod } from './types'; | ||
import { HTTPMethod, Interceptors, ValueOf } from './types'; | ||
import { MutableRefObject } from 'react'; | ||
@@ -7,3 +7,3 @@ interface RouteAndOptions { | ||
} | ||
export default function makeRouteAndOptions(initialOptions: RequestInit, method: HTTPMethod, controller: MutableRefObject<AbortController | null | undefined>, routeOrBody?: string | BodyInit | object, bodyAs2ndParam?: BodyInit | object): RouteAndOptions; | ||
export default function makeRouteAndOptions(initialOptions: RequestInit, method: HTTPMethod, controller: MutableRefObject<AbortController | null | undefined>, routeOrBody?: string | BodyInit | object, bodyAs2ndParam?: BodyInit | object, requestInterceptor?: ValueOf<Pick<Interceptors, 'request'>>): Promise<RouteAndOptions>; | ||
export {}; |
"use strict"; | ||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } | ||
return new (P || (P = Promise))(function (resolve, reject) { | ||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } | ||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } | ||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } | ||
step((generator = generator.apply(thisArg, _arguments || [])).next()); | ||
}); | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
@@ -6,46 +15,50 @@ const types_1 = require("./types"); | ||
const { GET, OPTIONS } = types_1.HTTPMethod; | ||
function makeRouteAndOptions(initialOptions, method, controller, routeOrBody, bodyAs2ndParam) { | ||
utils_1.invariant(!(utils_1.isObject(routeOrBody) && utils_1.isObject(bodyAs2ndParam)), `If first argument of ${method.toLowerCase()}() is an object, you cannot have a 2nd argument. 😜`); | ||
utils_1.invariant(!(method === GET && utils_1.isObject(routeOrBody)), `You can only have query params as 1st argument of request.get()`); | ||
utils_1.invariant(!(method === GET && bodyAs2ndParam !== undefined), `You can only have query params as 1st argument of request.get()`); | ||
const route = (() => { | ||
if (utils_1.isBrowser && routeOrBody instanceof URLSearchParams) | ||
return `?${routeOrBody}`; | ||
if (utils_1.isString(routeOrBody)) | ||
return routeOrBody; | ||
return ''; | ||
})(); | ||
const body = (() => { | ||
if (utils_1.isObject(routeOrBody)) | ||
return JSON.stringify(routeOrBody); | ||
if (utils_1.isObject(bodyAs2ndParam)) | ||
return JSON.stringify(bodyAs2ndParam); | ||
if (utils_1.isBrowser && | ||
(bodyAs2ndParam instanceof FormData || | ||
bodyAs2ndParam instanceof URLSearchParams)) | ||
return bodyAs2ndParam; | ||
return JSON.stringify({}); | ||
})(); | ||
const options = (() => { | ||
const opts = Object.assign(Object.assign({}, initialOptions), { body, | ||
method, signal: controller.current ? controller.current.signal : null, headers: Object.assign({ | ||
// default content types http://bit.ly/2N2ovOZ | ||
// Accept: 'application/json', | ||
'Content-Type': 'application/json' }, initialOptions.headers) }); | ||
if (utils_1.isBrowser && | ||
(routeOrBody instanceof URLSearchParams || | ||
routeOrBody instanceof FormData || | ||
bodyAs2ndParam instanceof FormData)) { | ||
delete opts.headers['Content-Type']; | ||
} | ||
if (method === GET || method === OPTIONS) | ||
delete opts.body; | ||
return opts; | ||
})(); | ||
return { | ||
route, | ||
options, | ||
}; | ||
function makeRouteAndOptions(initialOptions, method, controller, routeOrBody, bodyAs2ndParam, requestInterceptor) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
utils_1.invariant(!(utils_1.isObject(routeOrBody) && utils_1.isObject(bodyAs2ndParam)), `If first argument of ${method.toLowerCase()}() is an object, you cannot have a 2nd argument. 😜`); | ||
utils_1.invariant(!(method === GET && utils_1.isObject(routeOrBody)), `You can only have query params as 1st argument of request.get()`); | ||
utils_1.invariant(!(method === GET && bodyAs2ndParam !== undefined), `You can only have query params as 1st argument of request.get()`); | ||
const route = (() => { | ||
if (utils_1.isBrowser && routeOrBody instanceof URLSearchParams) | ||
return `?${routeOrBody}`; | ||
if (utils_1.isString(routeOrBody)) | ||
return routeOrBody; | ||
return ''; | ||
})(); | ||
const body = (() => { | ||
if (utils_1.isObject(routeOrBody)) | ||
return JSON.stringify(routeOrBody); | ||
if (utils_1.isObject(bodyAs2ndParam)) | ||
return JSON.stringify(bodyAs2ndParam); | ||
if (utils_1.isBrowser && | ||
(bodyAs2ndParam instanceof FormData || | ||
bodyAs2ndParam instanceof URLSearchParams)) | ||
return bodyAs2ndParam; | ||
return JSON.stringify({}); | ||
})(); | ||
const options = yield (() => __awaiter(this, void 0, void 0, function* () { | ||
const opts = Object.assign(Object.assign({}, initialOptions), { body, | ||
method, signal: controller.current ? controller.current.signal : null, headers: Object.assign({ | ||
// default content types http://bit.ly/2N2ovOZ | ||
// Accept: 'application/json', | ||
'Content-Type': 'application/json' }, initialOptions.headers) }); | ||
if (utils_1.isBrowser && | ||
(routeOrBody instanceof URLSearchParams || | ||
routeOrBody instanceof FormData || | ||
bodyAs2ndParam instanceof FormData)) { | ||
delete opts.headers['Content-Type']; | ||
} | ||
if (method === GET || method === OPTIONS) | ||
delete opts.body; | ||
if (requestInterceptor) | ||
return yield requestInterceptor(opts); | ||
return opts; | ||
}))(); | ||
return { | ||
route, | ||
options, | ||
}; | ||
}); | ||
} | ||
exports.default = makeRouteAndOptions; | ||
//# sourceMappingURL=makeRouteAndOptions.js.map |
@@ -12,3 +12,3 @@ export declare enum HTTPMethod { | ||
url?: string; | ||
options?: RequestInit | undefined; | ||
options?: Options | undefined; | ||
graphql?: boolean; | ||
@@ -56,2 +56,6 @@ } | ||
export declare type UseFetch<TData> = UseFetchArrayReturn<TData> & UseFetchObjectReturn<TData>; | ||
export declare type Interceptors = { | ||
request?: (options: Options) => Options; | ||
response?: (response: Res<any>) => any; | ||
}; | ||
export interface CustomOptions { | ||
@@ -61,5 +65,6 @@ onMount?: boolean; | ||
path?: string; | ||
url: string; | ||
url?: string; | ||
loading?: boolean; | ||
data?: any; | ||
interceptors?: Interceptors; | ||
} | ||
@@ -74,1 +79,2 @@ export declare type Options = CustomOptions & Omit<RequestInit, 'body'> & { | ||
export declare type OptionsOverwriteWithContext = (options: Options) => Options; | ||
export declare type ValueOf<T> = T[keyof T]; |
@@ -1,2 +0,10 @@ | ||
import { OptionsMaybeURL, NoUrlOptions, CustomOptions } from './types'; | ||
import { OptionsMaybeURL, NoUrlOptions, Interceptors } from './types'; | ||
declare type UseCustomOptions = { | ||
onMount: boolean; | ||
path: string; | ||
url: string; | ||
loading: boolean; | ||
data?: any; | ||
interceptors: Interceptors; | ||
}; | ||
/** | ||
@@ -11,2 +19,3 @@ * Handles all special options. | ||
*/ | ||
export default function useCustomOptions(urlOrOptions?: string | OptionsMaybeURL, optionsNoURLs?: NoUrlOptions): CustomOptions; | ||
export default function useCustomOptions(urlOrOptions?: string | OptionsMaybeURL, optionsNoURLs?: NoUrlOptions): UseCustomOptions; | ||
export {}; |
@@ -10,4 +10,2 @@ "use strict"; | ||
const FetchContext_1 = __importDefault(require("./FetchContext")); | ||
// Provider ex: useFetch({ url: 'https://url.com' }) -- (overwrites global url) | ||
// TODO - Provider: arg1 = oldGlobalOptions => ({ my: 'new local options'}) (overwrite all global options for this instance of useFetch) | ||
/** | ||
@@ -24,2 +22,3 @@ * Handles all special options. | ||
const context = react_1.useContext(FetchContext_1.default); | ||
const contextInterceptors = context.options && context.options.interceptors || {}; | ||
const { isServer } = use_ssr_1.default(); | ||
@@ -66,5 +65,21 @@ utils_1.invariant(!(utils_1.isObject(urlOrOptions) && utils_1.isObject(optionsNoURLs)), 'You cannot have a 2nd parameter of useFetch when your first argument is an object config.'); | ||
}, [urlOrOptions, optionsNoURLs]); | ||
return { url, onMount, loading, data, path }; | ||
const interceptors = react_1.useMemo(() => { | ||
const final = Object.assign({}, contextInterceptors); | ||
if (utils_1.isObject(urlOrOptions) && utils_1.isObject(urlOrOptions.interceptors)) { | ||
if (urlOrOptions.interceptors.request) | ||
final.request = urlOrOptions.interceptors.request; | ||
if (urlOrOptions.interceptors.response) | ||
final.response = urlOrOptions.interceptors.response; | ||
} | ||
if (utils_1.isObject(optionsNoURLs) && utils_1.isObject(optionsNoURLs.interceptors)) { | ||
if (optionsNoURLs.interceptors.request) | ||
final.request = optionsNoURLs.interceptors.request; | ||
if (optionsNoURLs.interceptors.response) | ||
final.response = optionsNoURLs.interceptors.response; | ||
} | ||
return final; | ||
}, [urlOrOptions, optionsNoURLs]); | ||
return { url, onMount, loading, data, path, interceptors }; | ||
} | ||
exports.default = useCustomOptions; | ||
//# sourceMappingURL=useCustomOptions.js.map |
@@ -32,2 +32,3 @@ "use strict"; | ||
const makeRouteAndOptions_1 = __importDefault(require("./makeRouteAndOptions")); | ||
const utils_1 = require("./utils"); | ||
// No <Provider url='example.com' /> | ||
@@ -42,3 +43,3 @@ // function useFetch<TData = any>(url: string, options?: NoUrlOptions): UseFetch<TData> | ||
function useFetch(...args) { | ||
const _a = useCustomOptions_1.default(...args), { url, onMount, path } = _a, defaults = __rest(_a, ["url", "onMount", "path"]); | ||
const _a = useCustomOptions_1.default(...args), { url, onMount, path, interceptors } = _a, defaults = __rest(_a, ["url", "onMount", "path", "interceptors"]); | ||
const requestInit = useRequestInit_1.default(...args); | ||
@@ -54,7 +55,8 @@ const { isBrowser, isServer } = use_ssr_1.default(); | ||
controller.current = isBrowser ? new AbortController() : null; | ||
const { route, options } = makeRouteAndOptions_1.default(requestInit, method, controller, routeOrBody, body); | ||
setLoading(true); | ||
if (error) | ||
setError(undefined); | ||
let { route, options } = yield makeRouteAndOptions_1.default(requestInit, method, controller, routeOrBody, body, interceptors.request); | ||
let theData; | ||
try { | ||
setLoading(true); | ||
if (error) | ||
setError(undefined); | ||
if (isServer) | ||
@@ -64,6 +66,6 @@ return; // TODO: for now, we don't do anything on the server | ||
try { | ||
data.current = yield res.current.json(); | ||
theData = yield res.current.json(); | ||
} | ||
catch (err) { | ||
data.current = (yield res.current.text()); // FIXME: should not be `any` type | ||
theData = (yield res.current.text()); // FIXME: should not be `any` type | ||
} | ||
@@ -76,2 +78,3 @@ } | ||
finally { | ||
data.current = (defaults.data && utils_1.isEmpty(theData)) ? defaults.data : theData; | ||
controller.current = null; | ||
@@ -101,3 +104,4 @@ setLoading(false); | ||
}; | ||
const response = Object.assign({ data: data.current }, res.current); | ||
const responseObj = Object.assign({ data: data.current }, res.current); | ||
const response = interceptors.response ? interceptors.response(responseObj) : responseObj; | ||
// handling onMount | ||
@@ -104,0 +108,0 @@ const mounted = react_1.useRef(false); |
@@ -11,2 +11,3 @@ "use strict"; | ||
const context = react_1.useContext(FetchContext_1.default); | ||
const contextRequestInit = utils_1.pullOutRequestInit(context.options); | ||
const requestInitOptions = utils_1.isObject(urlOrOptions) | ||
@@ -18,8 +19,8 @@ ? urlOrOptions | ||
const requestInit = utils_1.pullOutRequestInit(requestInitOptions); | ||
return Object.assign(Object.assign(Object.assign({}, context.options), requestInit), { headers: Object.assign(Object.assign({ | ||
return Object.assign(Object.assign(Object.assign({}, contextRequestInit), requestInit), { headers: Object.assign(Object.assign({ | ||
// default content types http://bit.ly/2N2ovOZ | ||
// Accept: 'application/json', | ||
'Content-Type': 'application/json' }, (context.options || {}).headers), requestInit.headers) }); | ||
'Content-Type': 'application/json' }, contextRequestInit.headers), requestInit.headers) }); | ||
} | ||
exports.default = useRequestInit; | ||
//# sourceMappingURL=useRequestInit.js.map |
@@ -19,3 +19,4 @@ import { OptionsMaybeURL } from './types'; | ||
export declare const pullOutRequestInit: (options?: OptionsMaybeURL | undefined) => RequestInit; | ||
export declare const isEmpty: (x: any) => boolean; | ||
export declare const isBrowser: boolean; | ||
export declare const isServer: boolean; |
@@ -82,2 +82,3 @@ "use strict"; | ||
}; | ||
exports.isEmpty = (x) => x === undefined || x === null; | ||
exports.isBrowser = !!(typeof window !== 'undefined' && | ||
@@ -84,0 +85,0 @@ window.document && |
{ | ||
"name": "use-http", | ||
"version": "0.1.82", | ||
"version": "0.1.83", | ||
"homepage": "http://use-http.com", | ||
@@ -5,0 +5,0 @@ "main": "dist/index.js", |
106
README.md
@@ -74,2 +74,3 @@ <a href="http://use-http.com"> | ||
- Provider to set default `url` and `options` | ||
- Request/response interceptors <!--https://github.com/alex-cory/use-http#user-content-interceptors--> | ||
@@ -157,3 +158,3 @@ Usage | ||
<details open><summary>Basic Usage with <code>Provider</code></summary> | ||
<details open><summary><b>Basic Usage with <code>Provider</code></b></summary> | ||
@@ -430,2 +431,36 @@ ```js | ||
<details id='interceptors'><summary><b>Request/Response Interceptors</b></summary> | ||
This example shows how we can do authentication in the `request` interceptor and how we can camelCase the results in the `response` interceptor | ||
```jsx | ||
import { Provider } from 'use-http' | ||
import camelCase from 'camelcase-keys-recursive' | ||
function App() { | ||
let [token] = useLocalStorage('token') | ||
const options = { | ||
interceptors: { | ||
// every time we make an http request, this will run 1st before the request is made | ||
request: async (options) => { | ||
if (isExpired(token)) token = await getNewToken() | ||
options.headers.Authorization = `Bearer ${token}` | ||
return options | ||
}, | ||
// every time we make an http request, before getting the response back, this will run | ||
response: (response) => camelCase(response) | ||
} | ||
} | ||
return ( | ||
<Provider url='http://example.com' options={options}> | ||
<SomeComponent /> | ||
<Provider/> | ||
) | ||
} | ||
``` | ||
</details> | ||
Overview | ||
@@ -455,2 +490,4 @@ -------- | ||
| `loading` | Allows you to set default value for `loading` | `false` unless `onMount === true` | | ||
| `interceptors.request` | Allows you to do something before an http request is sent out. Useful for authentication if you need to refresh tokens a lot. | `undefined` | | ||
| `interceptors.response` | Allows you to do something after an http response is recieved. Useful for something like camelCasing the keys of the response. | `undefined` | | ||
@@ -460,6 +497,14 @@ ```jsx | ||
// accepts all `fetch` options such as headers, method, etc. | ||
url: 'https://example.com', // used to be `baseUrl` | ||
url: 'https://example.com', // used to be `baseUrl` | ||
onMount: true, | ||
data: [], // default for `data` field | ||
loading: false, // default for `loading` field | ||
data: [], // default for `data` field | ||
loading: false, // default for `loading` field | ||
interceptors: { // typically, `interceptors` would be added as an option to the `<Provider />` | ||
request: async (options) => { // `async` is not required | ||
return options // returning the `options` is important | ||
}, | ||
response: (response) => { | ||
return response // returning the `response` is important | ||
} | ||
} | ||
}) | ||
@@ -487,46 +532,2 @@ ``` | ||
- [ ] show comparison with Apollo | ||
- [ ] Interceptors (potential syntax example) this shows how to get access tokens on each request if an access token or refresh token is expired | ||
```jsx | ||
const App = () => { | ||
const { get } = useFetch('https://example.com') | ||
const [accessToken, setAccessToken] = useLocalStorage('access-token') | ||
const [refreshToken, setRefreshToken] = useLocalStorage('refresh-token') | ||
const { history } = useReactRouter() | ||
const options = { | ||
interceptors: { | ||
async request(opts) { | ||
let headers = {} | ||
// refresh token expires in 1 day, used to get access token | ||
if (!refreshToken || isExpired(refreshToken)) { | ||
return history.push('/login') | ||
} | ||
// access token expires every 15 minutes, use refresh token to get new access token | ||
if (!accessToken || isExpired(accessToken)) { | ||
const access = await get(`/access-token?refreshToken=${refreshToken}`) | ||
setAccessToken(access) | ||
headers = { | ||
Authorization: `Bearer ${access}`, | ||
} | ||
} | ||
const finalOptions = { | ||
...opts, | ||
headers: { | ||
...opts.headers, | ||
...headers, | ||
}, | ||
} | ||
return finalOptions | ||
}, | ||
}, | ||
headers: { | ||
Authorization: `Bearer ${accessToken}` | ||
} | ||
} | ||
return ( | ||
<Provider url='https://example.com' options={options}> | ||
<App /> | ||
</Provider> | ||
) | ||
} | ||
``` | ||
- [ ] Dedupe requests done to the same endpoint. Only one request to the same endpoint will be initiated. [ref](https://www.npmjs.com/package/@bjornagh/use-fetch) | ||
@@ -556,3 +557,2 @@ - [ ] Cache responses to improve speed and reduce amount of requests | ||
onUpdate: [props.id] // everytime props.id is updated, it will re-run the request GET in this case | ||
path: '/todos' // this would allow you to POST and GET to the same path onMount and on demand if you had a url in context | ||
retry: 3, // amount of times it should retry before erroring out | ||
@@ -564,10 +564,2 @@ retryDuration: 1000, // amount of time for each retry before timing out? | ||
mutation: `some graphql mutation` // if you would prefer to pass the mutation in the config | ||
interceptors: { | ||
request(opts) { // i.e. if you need to do some kind of authentication before a request | ||
return opts | ||
} | ||
response(res) { // i.e. if you want to camelCase all fields in a response everytime | ||
return res | ||
} | ||
} | ||
}) | ||
@@ -574,0 +566,0 @@ ``` |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
69908
690
631