From 0819e61b10bf4eedc051c6acbc5c6b9062110607 Mon Sep 17 00:00:00 2001 From: Alex Cory Date: Mon, 20 Apr 2020 23:54:22 -0700 Subject: [PATCH 01/18] first go --- src/__tests__/doFetchArgs.test.tsx | 24 ++-- src/__tests__/useFetch.test.tsx | 81 +++++------- src/__tests__/useFetchArgs.test.tsx | 66 +++------- src/defaults.ts | 10 +- src/doFetchArgs.ts | 18 +-- src/types.ts | 83 +++++++----- src/useFetch.ts | 17 ++- src/useFetchArgs.ts | 187 ++++++++++------------------ src/useMutation.ts | 1 + src/useQuery.ts | 1 + src/utils.ts | 4 +- 11 files changed, 206 insertions(+), 286 deletions(-) diff --git a/src/__tests__/doFetchArgs.test.tsx b/src/__tests__/doFetchArgs.test.tsx index 55d13dc8..76cea4c1 100644 --- a/src/__tests__/doFetchArgs.test.tsx +++ b/src/__tests__/doFetchArgs.test.tsx @@ -18,12 +18,12 @@ describe('doFetchArgs: general usages', (): void => { }) const { url, options } = await doFetchArgs( {}, - '', - '', HTTPMethod.POST, controller, defaults.cacheLife, cache, + '', + '', expectedRoute, {} ) @@ -47,12 +47,12 @@ describe('doFetchArgs: general usages', (): void => { }) const { options, url } = await doFetchArgs( {}, - 'https://example.com', - '', HTTPMethod.POST, controller, defaults.cacheLife, cache, + 'https://example.com', + '', '/test', [] ) @@ -76,12 +76,12 @@ describe('doFetchArgs: general usages', (): void => { }) const { url } = await doFetchArgs( {}, - 'https://example.com', - '/path', HTTPMethod.POST, controller, defaults.cacheLife, cache, + 'https://example.com', + '/path', '/route', {} ) @@ -103,12 +103,12 @@ describe('doFetchArgs: general usages', (): void => { } const { options } = await doFetchArgs( {}, - '', - '', HTTPMethod.POST, controller, defaults.cacheLife, cache, + undefined, + '', '/test', {}, interceptors.request @@ -154,12 +154,12 @@ describe('doFetchArgs: Errors', (): void => { await expect( doFetchArgs( {}, - '', - '', HTTPMethod.GET, controller, defaults.cacheLife, cache, + '', + '', {}, {} ) @@ -196,12 +196,12 @@ describe('doFetchArgs: Errors', (): void => { await expect( doFetchArgs( {}, - '', - '', HTTPMethod.GET, controller, defaults.cacheLife, cache, + '', + '', [], [] ) diff --git a/src/__tests__/useFetch.test.tsx b/src/__tests__/useFetch.test.tsx index 128ab646..3a882cd0 100644 --- a/src/__tests__/useFetch.test.tsx +++ b/src/__tests__/useFetch.test.tsx @@ -12,7 +12,7 @@ import mockConsole from 'jest-mock-console' import * as mockdate from 'mockdate' import defaults from '../defaults' -import { Res, Options, CachePolicies } from '../types' +import { Res, IncomingOptions, CachePolicies } from '../types' import { emptyCustomResponse, sleep, makeError } from '../utils' const fetch = global.fetch as FetchMock @@ -187,9 +187,9 @@ describe('useFetch - BROWSER - with ', (): void => { fetch.mockResponseOnce(JSON.stringify(expected)) }) - it('should work correctly: useFetch({ data: [] }, [])', async (): Promise => { + it(`should work correctly: useFetch('/', { data: [] }, [])`, async (): Promise => { const { result, waitForNextUpdate } = renderHook( - () => useFetch({ data: {} }, []), // onMount === true + () => useFetch('/', { data: {} }, []), // onMount === true { wrapper } ) @@ -202,7 +202,7 @@ describe('useFetch - BROWSER - with ', (): void => { it('should execute GET using Provider url', async (): Promise => { const { result, waitForNextUpdate } = renderHook( - () => useFetch({ data: {} }, []), // onMount === true + () => useFetch('/', { data: {} }, []), // onMount === true { wrapper } ) @@ -232,8 +232,7 @@ describe('useFetch - BROWSER - with ', (): void => { it('should merge the data onNewData for pagination', async (): Promise => { const { result, waitForNextUpdate } = renderHook( - () => useFetch({ - path: '/people', + () => useFetch('/people', { data: { no: 'way' }, onNewData: (currData, newData) => ({ ...currData, ...newData }) }, []), // onMount === true @@ -246,19 +245,19 @@ describe('useFetch - BROWSER - with ', (): void => { ...expected, no: 'way' }) + result.current.cache.clear() }) it('should not make another request when there is no more data `perPage` pagination', async (): Promise => { fetch.resetMocks() const expected1 = [1, 2, 3] - fetch.mockResponse(JSON.stringify(expected1)) + fetch.mockResponseOnce(JSON.stringify(expected1)) + .mockResponseOnce(JSON.stringify([4])) const { result, rerender, waitForNextUpdate } = renderHook( ({ page }) => useFetch(`https://example.com?page=${page}`, { data: [], perPage: 3, onNewData: (currData, newData) => { - // to imitate getting a response with less items - if (page === 2) return [...currData, 4] return [...currData, ...newData] } }, [page]), // onMount === true @@ -276,16 +275,15 @@ describe('useFetch - BROWSER - with ', (): void => { expect(result.current.data).toEqual([...expected1, 4]) expect(fetch.mock.calls.length).toBe(2) act(() => rerender({ page: 3 })) - await waitForNextUpdate() expect(result.current.data).toEqual([...expected1, 4]) expect(fetch.mock.calls.length).toBe(2) }) - it('should execute GET using Provider url: useFetch({ path: "/people" }, [])', async (): Promise< + it(`should execute GET using Provider url: useFetch('/people', [])`, async (): Promise< void > => { const { result, waitForNextUpdate } = renderHook( - () => useFetch({ path: '/people' }, []), // onMount === true + () => useFetch('/people', []), // onMount === true { wrapper } ) expect(result.current.loading).toBe(true) @@ -322,7 +320,7 @@ describe('timeouts', (): void => { const onAbort = jest.fn() const onTimeout = jest.fn() const { result, waitForNextUpdate } = renderHook( - () => useFetch({ + () => useFetch('/', { timeout, onAbort, onTimeout @@ -349,13 +347,12 @@ describe('timeouts', (): void => { const onAbort = jest.fn() const onTimeout = jest.fn() const { result, waitForNextUpdate } = renderHook( - () => useFetch({ + () => useFetch('/todos', { retries: 1, // TODO: this test times out if `retryDelay > 0` // works in apps, not sure how to advance the timers correctly retryDelay: 0, timeout, - path: '/todos', onAbort, onTimeout }, []), // onMount === true @@ -496,8 +493,7 @@ describe('useFetch - BROWSER - with - Managed State', (): void => { it('should re-run the request when onUpdate dependencies are updated', async (): Promise => { const { result, waitForNextUpdate, rerender } = renderHook( - ({ initialValue }) => useFetch({ - path: `/${initialValue}`, + ({ initialValue }) => useFetch(`/${initialValue}`, { data: {} }, [initialValue]), // (onMount && onUpdate) === true { @@ -520,8 +516,7 @@ describe('useFetch - BROWSER - with - Managed State', (): void => { it('should fetch cached data when cached path is requested', async (): Promise => { const { result, waitForNextUpdate, rerender } = renderHook( - ({ initialValue }) => useFetch({ - path: `/a/${initialValue}`, + ({ initialValue }) => useFetch(`/a/${initialValue}`, { data: {} }, [initialValue]), // (onMount && onUpdate) === true { @@ -554,21 +549,11 @@ describe('useFetch - BROWSER - interceptors', (): void => { const snake_case = { title: 'Alex Cory', first_name: 'Alex' } const expected = { title: 'Alex Cory', firstName: 'Alex' } + const request = jest.fn(({ options }) => options) const wrapper = ({ children }: { children?: ReactNode }): ReactElement => { - const options: Options = { + const options: IncomingOptions = { interceptors: { - request: async ({ options: opts, url, path, route }) => { - if (path === '/path') { - opts.data = 'path' - } - if (url === 'url') { - opts.data = 'url' - } - if (route === '/route') { - opts.data = 'route' - } - return opts - }, + request, async response({ response: res }) { if (res.data) res.data = toCamel(res.data) return res @@ -584,6 +569,7 @@ describe('useFetch - BROWSER - interceptors', (): void => { afterEach((): void => { fetch.resetMocks() cleanup() + request.mockClear() }) beforeEach((): void => { @@ -612,11 +598,12 @@ describe('useFetch - BROWSER - interceptors', (): void => { it('should pass the proper path string to `interceptors.request`', async (): Promise => { const { result } = renderHook( - () => useFetch({ path: '/path' }), + () => useFetch('/path'), { wrapper } ) await act(result.current.get) - expect((fetch.mock.calls[0][1] as any).data).toEqual('path') + expect(fetch.mock.calls[0][0]).toBe('https://example.com/path') + expect(request.mock.calls[0][0].path).toBe('/path') }) it('should pass the proper route string to `interceptors.request`', async (): Promise => { @@ -627,16 +614,18 @@ describe('useFetch - BROWSER - interceptors', (): void => { await act(async () => { await result.current.get('/route') }) - expect((fetch.mock.calls[0][1] as any).data).toEqual('route') + expect(fetch.mock.calls[0][0]).toBe('https://example.com/route') + expect(request.mock.calls[0][0].route).toBe('/route') }) it('should pass the proper url string to `interceptors.request`', async (): Promise => { const { result } = renderHook( - () => useFetch('url'), + () => useFetch(), { wrapper } ) await act(result.current.get) - expect((fetch.mock.calls[0][1] as any).data).toEqual('url') + expect(fetch.mock.calls[0][0]).toBe('https://example.com') + expect(request.mock.calls[0][0].url).toBe('https://example.com') }) it('should still call both interceptors when using cache', async (): Promise => { @@ -1060,8 +1049,7 @@ describe('useFetch - BROWSER - errors', (): void => { fetch.resetMocks() fetch.mockResponse('fail', { status: 401 }) const { result } = renderHook( - () => useFetch({ - url: 'https://example.com', + () => useFetch('https://example.com', { data: [], cachePolicy: NO_CACHE }) @@ -1094,8 +1082,7 @@ describe('useFetch - BROWSER - errors', (): void => { fetch.resetMocks() fetch.mockReject(expectedError) const { result } = renderHook( - () => useFetch({ - url: 'https://example.com/2', + () => useFetch('https://example.com/2', { data: [], cachePolicy: NO_CACHE }) @@ -1177,7 +1164,7 @@ describe('useFetch - BROWSER - persistence', (): void => { it('should fetch once', async (): Promise => { const { waitForNextUpdate } = renderHook( - () => useFetch({ url: 'https://persist.com', persist: true }, []) + () => useFetch('https://persist.com', { persist: true }, []) ) await waitForNextUpdate() expect(fetch).toHaveBeenCalledTimes(1) @@ -1187,7 +1174,7 @@ describe('useFetch - BROWSER - persistence', (): void => { fetch.mockResponse(JSON.stringify(unexpected)) const { result, waitForNextUpdate } = renderHook( - () => useFetch({ url: 'https://persist.com', persist: true }, []) + () => useFetch('https://persist.com', { persist: true }, []) ) await waitForNextUpdate() expect(fetch).toHaveBeenCalledTimes(0) @@ -1204,7 +1191,7 @@ describe('useFetch - BROWSER - persistence', (): void => { mockdate.set('2020-01-02 02:00:00') const { waitForNextUpdate } = renderHook( - () => useFetch({ url: 'https://persist.com', persist: true }, []) + () => useFetch('https://persist.com', { persist: true }, []) ) await waitForNextUpdate() expect(fetch).toHaveBeenCalledTimes(1) @@ -1212,7 +1199,7 @@ describe('useFetch - BROWSER - persistence', (): void => { it('should have `cache` in the return of useFetch', async (): Promise => { const { result } = renderHook( - () => useFetch({ url: 'https://persist.com', persist: true }) + () => useFetch('https://persist.com', { persist: true }) ) expect(result.current.cache).toBeDefined() expect(result.current.cache.get).toBeInstanceOf(Function) @@ -1224,14 +1211,14 @@ describe('useFetch - BROWSER - persistence', (): void => { it('should error if passing wrong cachePolicy with persist: true', async (): Promise => { var { result } = renderHook( - () => useFetch({ url: 'https://persist.com', persist: true, cachePolicy: NO_CACHE }, []) + () => useFetch('https://persist.com', { persist: true, cachePolicy: NO_CACHE }, []) ) expect(result.error.name).toBe('Invariant Violation') expect(result.error.message).toBe('You cannot use option \'persist\' with cachePolicy: no-cache 🙅‍♂️') // eslint-disable-next-line var { result } = renderHook( - () => useFetch({ url: 'https://persist.com', persist: true, cachePolicy: NETWORK_ONLY }, []) + () => useFetch('https://persist.com', { persist: true, cachePolicy: NETWORK_ONLY }, []) ) expect(result.error.name).toBe('Invariant Violation') expect(result.error.message).toBe('You cannot use option \'persist\' with cachePolicy: network-only 🙅‍♂️') diff --git a/src/__tests__/useFetchArgs.test.tsx b/src/__tests__/useFetchArgs.test.tsx index 701bc2f5..1d865dee 100644 --- a/src/__tests__/useFetchArgs.test.tsx +++ b/src/__tests__/useFetchArgs.test.tsx @@ -19,26 +19,22 @@ describe('useFetchArgs: general usages', (): void => { ) expect(result.current).toEqual({ ...useFetchArgsDefaults, + host: 'https://example.com', customOptions: { ...useFetchArgsDefaults.customOptions, - url: 'https://example.com' } }) }) it('should create custom options with 1st arg as config object with `onMount: true`', (): void => { const { result } = renderHook((): any => - useFetchArgs({ - url: 'https://example.com' - }, []) // onMount === true + useFetchArgs('https://example.com', []) // onMount === true ) expect(result.current).toEqual({ ...useFetchArgsDefaults, + host: 'https://example.com', customOptions: { ...useFetchArgsDefaults.customOptions, - url: 'https://example.com' - }, - defaults: { loading: true, data: undefined }, @@ -50,9 +46,9 @@ describe('useFetchArgs: general usages', (): void => { const { result } = renderHook((): any => useFetchArgs(), { wrapper }) expect(result.current).toStrictEqual({ ...useFetchArgsDefaults, + host: 'https://example.com', customOptions: { ...useFetchArgsDefaults.customOptions, - url: 'https://example.com' } }) }) @@ -64,11 +60,9 @@ describe('useFetchArgs: general usages', (): void => { ) expect(result.current).toStrictEqual({ ...useFetchArgsDefaults, + host: 'https://cool.com', customOptions: { ...useFetchArgsDefaults.customOptions, - url: 'https://cool.com' - }, - defaults: { loading: true, data: undefined }, @@ -78,19 +72,19 @@ describe('useFetchArgs: general usages', (): void => { it('should set default data === []', (): void => { const { result } = renderHook( - (): any => useFetchArgs({ data: [] }), + (): any => useFetchArgs('https://cool.com', { + data: [] + }), { wrapper } ) expect(result.current).toStrictEqual({ ...useFetchArgsDefaults, + host: 'https://cool.com', customOptions: { ...useFetchArgsDefaults.customOptions, - url: 'https://example.com' - }, - defaults: { loading: false, data: [] - } + }, }) }) @@ -105,9 +99,9 @@ describe('useFetchArgs: general usages', (): void => { const expected = { ...useFetchArgsDefaults, + host: 'http://localhost', customOptions: { ...useFetchArgsDefaults.customOptions, - url: 'http://localhost' } } expect(result.current).toEqual(expected) @@ -130,7 +124,7 @@ describe('useFetchArgs: general usages', (): void => { ) const { result } = renderHook( - (): any => useFetchArgs(), + (): any => useFetchArgs('/path'), { wrapper: wrapper2 } ) @@ -176,12 +170,12 @@ describe('useFetchArgs: general usages', (): void => { it('should create custom options with `Content-Type: application/text`', (): void => { const options = { headers: { 'Content-Type': 'application/text' } } - const { result } = renderHook((): any => useFetchArgs(options), { wrapper }) + const { result } = renderHook((): any => useFetchArgs('https://example.com', options), { wrapper }) expect(result.current).toStrictEqual({ ...useFetchArgsDefaults, + host: 'https://example.com', customOptions: { ...useFetchArgsDefaults.customOptions, - url: 'https://example.com' }, requestInit: { ...options, @@ -198,12 +192,12 @@ describe('useFetchArgs: general usages', (): void => { const wrapper = ({ children }: { children?: ReactNode }): ReactElement => ( {children} ) - const { result } = renderHook((): any => useFetchArgs(), { wrapper }) + const { result } = renderHook((): any => useFetchArgs('http://localhost'), { wrapper }) expect(result.current).toStrictEqual({ ...useFetchArgsDefaults, + host: 'http://localhost', customOptions: { ...useFetchArgsDefaults.customOptions, - url: 'http://localhost' }, requestInit: { headers: { @@ -234,9 +228,9 @@ describe('useFetchArgs: general usages', (): void => { ) expect(result.current).toStrictEqual({ ...useFetchArgsDefaults, + host: 'http://localhost', customOptions: { ...useFetchArgsDefaults.customOptions, - url: 'http://localhost' }, requestInit: { ...overwriteProviderOptions, @@ -250,35 +244,11 @@ describe('useFetchArgs: general usages', (): void => { }) describe('useFetchArgs: Errors', (): void => { - it('should error if no url string is set and no Provider is in place', (): void => { - const { result } = renderHook((): any => useFetchArgs()) - expect(result.error.name).toBe('Invariant Violation') - expect(result.error.message).toBe( - 'The first argument of useFetch is required unless you have a global url setup like: ' - ) - }) - - it('should error if 1st arg is object and no URL field is set in it', (): void => { - const { result } = renderHook((): any => useFetchArgs({})) - expect(result.error.name).toBe('Invariant Violation') - expect(result.error.message).toBe( - 'The first argument of useFetch is required unless you have a global url setup like: ' - ) - }) - - it('should error if no URL is specified', (): void => { - const { result } = renderHook((): any => useFetchArgs('')) - expect(result.error.name).toBe('Invariant Violation') - expect(result.error.message).toBe( - 'The first argument of useFetch is required unless you have a global url setup like: ' - ) - }) - it('should error if 1st and 2nd arg are both objects', (): void => { const { result } = renderHook((): any => useFetchArgs({}, {})) expect(result.error.name).toBe('Invariant Violation') expect(result.error.message).toBe( - 'You cannot have a 2nd parameter of useFetch when your first argument is an object config.' + 'You cannot have a 2nd parameter of useFetch as object when your first argument is an object.' ) }) diff --git a/src/defaults.ts b/src/defaults.ts index 9edb00cb..d6ae8e7c 100644 --- a/src/defaults.ts +++ b/src/defaults.ts @@ -3,6 +3,8 @@ import { isObject } from './utils' export const useFetchArgsDefaults: UseFetchArgsReturn = { + host: '', + path: undefined, customOptions: { cacheLife: 0, cachePolicy: CachePolicies.CACHE_FIRST, @@ -11,7 +13,6 @@ export const useFetchArgsDefaults: UseFetchArgsReturn = { onError: () => { /* do nothing */ }, onNewData: (currData: any, newData: any) => newData, onTimeout: () => { /* do nothing */ }, - path: '', perPage: 0, persist: false, responseType: ['json', 'text', 'blob', 'arrayBuffer'], @@ -20,17 +21,14 @@ export const useFetchArgsDefaults: UseFetchArgsReturn = { retryOn: [], suspense: false, timeout: 0, - url: '', + data: undefined, + loading: false }, requestInit: { headers: { Accept: 'application/json, text/plain, */*' } }, - defaults: { - data: undefined, - loading: false - }, dependencies: undefined } diff --git a/src/doFetchArgs.ts b/src/doFetchArgs.ts index 6ff55854..f2ddd9b6 100644 --- a/src/doFetchArgs.ts +++ b/src/doFetchArgs.ts @@ -5,12 +5,12 @@ const { GET } = HTTPMethod export default async function doFetchArgs( initialOptions: RequestInit, - initialURL: string, - path: string, method: HTTPMethod, controller: AbortController, cacheLife: number, cache: Cache, + host?: string, + path?: string, routeOrBody?: string | BodyInit | object, bodyAs2ndParam?: BodyInit | object, requestInterceptor?: ValueOf> @@ -34,7 +34,7 @@ export default async function doFetchArgs( return '' })() - const url = `${initialURL}${path}${route}` + const url = `${host}${path ?? ''}${route ?? ''}` const body = ((): BodyInit | null => { if (isBodyObject(routeOrBody)) return JSON.stringify(routeOrBody) @@ -65,8 +65,8 @@ export default async function doFetchArgs( return headers })() - const options = await (async (): Promise => { - const opts = { + const options = await (async (route): Promise => { + const opts: RequestInit = { ...initialOptions, method, signal: controller.signal @@ -81,11 +81,15 @@ export default async function doFetchArgs( if (body !== null) opts.body = body if (requestInterceptor) { - const interceptor = await requestInterceptor({ options: opts, url: initialURL, path, route }) + if (url.includes('/route2')) { + console.log('route: ', route) + } + const interceptor = await requestInterceptor({ options: opts, url: host, path, route }) return interceptor as any } return opts - })() + })(route) + // console.log('options', options) // TODO: if the body is a file, and this is a large file, it might exceed the size // limit of the key size. Potential solution: base64 the body diff --git a/src/types.ts b/src/types.ts index db3ff2f0..1bd46486 100644 --- a/src/types.ts +++ b/src/types.ts @@ -75,13 +75,13 @@ export interface DoFetchArgs { export interface FetchContextTypes { url: string - options: Options + options: IncomingOptions graphql?: boolean } export interface FetchProviderProps { url?: string - options?: Options + options?: IncomingOptions graphql?: boolean children: ReactNode } @@ -141,7 +141,7 @@ export interface Res extends Response { export type Req = ReqMethods & ReqBase -export type UseFetchArgs = [(string | OptionsMaybeURL | OverwriteGlobalOptions)?, (NoUrlOptions | OverwriteGlobalOptions | any[])?, any[]?] +export type UseFetchArgs = [(string | IncomingOptions | OverwriteGlobalOptions)?, (IncomingOptions | OverwriteGlobalOptions | any[])?, any[]?] export type UseFetchArrayReturn = [ Req, @@ -160,7 +160,7 @@ export type UseFetch = UseFetchArrayReturn & UseFetchObjectReturn export type Interceptors = { - request?: ({ options, url, path, route }: { options: Options, url: string, path: string, route: string }) => Promise | Options + request?: ({ options, url, path, route }: { options: RequestInit, url?: string, path?: string, route?: string }) => Promise | RequestInit response?: ({ response }: { response: Res }) => Promise> } @@ -173,36 +173,54 @@ export type Cache = { clear: () => void } +// export interface CustomOptionsChill { +// cacheLife?: number +// cachePolicy?: CachePolicies +// data?: any +// interceptors?: Interceptors +// loading?: boolean +// onAbort?: () => void +// onError?: OnError +// onNewData?: (currData: any, newData: any) => any +// onTimeout?: () => void +// persist?: boolean +// perPage?: number +// responseType?: ResponseType +// retries?: number +// retryOn?: RetryOn +// retryDelay?: RetryDelay +// suspense?: boolean +// timeout?: number +// } + export interface CustomOptions { - cacheLife?: number - cachePolicy?: CachePolicies - data?: any - interceptors?: Interceptors - loading?: boolean - onAbort?: () => void - onError?: OnError - onNewData?: (currData: any, newData: any) => any - onTimeout?: () => void - path?: string - persist?: boolean - perPage?: number - responseType?: ResponseType - retries?: number - retryOn?: RetryOn - retryDelay?: RetryDelay - suspense?: boolean - timeout?: number - url?: string + cacheLife: number + cachePolicy: CachePolicies + data: any + interceptors: Interceptors + loading: boolean + onAbort: () => void + onError: OnError + onNewData: (currData: any, newData: any) => any + onTimeout: () => void + persist: boolean + perPage: number + responseType: ResponseType + retries: number + retryOn: RetryOn + retryDelay: RetryDelay + suspense: boolean + timeout: number } +// these are the possible options that can be passed +export type IncomingOptions = Partial & + Omit & { body?: BodyInit | object | null } +// these options have `context` and `defaults` applied so +// the values should all be filled export type Options = CustomOptions & Omit & { body?: BodyInit | object | null } -export type NoUrlOptions = Omit - -export type OptionsMaybeURL = NoUrlOptions & - Partial> & { url?: string } - // TODO: this is still yet to be implemented export type OverwriteGlobalOptions = (options: Options) => Options @@ -215,6 +233,8 @@ export type ResponseType = BodyInterfaceMethods | BodyInterfaceMethods[] export type OnError = ({ error }: { error: Error }) => void export type UseFetchArgsReturn = { + host: string + path?: string | null customOptions: { cacheLife: number cachePolicy: CachePolicies @@ -223,7 +243,6 @@ export type UseFetchArgsReturn = { onError: OnError onNewData: (currData: any, newData: any) => any onTimeout: () => void - path: string perPage: number persist: boolean responseType: ResponseType @@ -232,13 +251,11 @@ export type UseFetchArgsReturn = { retryOn: RetryOn | undefined suspense: boolean timeout: number - url: string - } - requestInit: RequestInit - defaults: { + // defaults loading: boolean data?: any } + requestInit: RequestInit dependencies?: any[] } diff --git a/src/useFetch.ts b/src/useFetch.ts index ff85361f..02920ec9 100644 --- a/src/useFetch.ts +++ b/src/useFetch.ts @@ -26,7 +26,7 @@ const { CACHE_FIRST } = CachePolicies function useFetch(...args: UseFetchArgs): UseFetch { - const { customOptions, requestInit, defaults, dependencies } = useFetchArgs(...args) + const { host, path, customOptions, requestInit, dependencies } = useFetchArgs(...args) const { cacheLife, cachePolicy, // 'cache-first' by default @@ -35,7 +35,6 @@ function useFetch(...args: UseFetchArgs): UseFetch { onError, onNewData, onTimeout, - path, perPage, persist, responseType, @@ -44,7 +43,7 @@ function useFetch(...args: UseFetchArgs): UseFetch { retryOn, suspense, timeout, - url: initialURL, + ...defaults // { data: TData, loading: boolean } } = customOptions const cache = useCache({ persist, cacheLife, cachePolicy }) @@ -68,19 +67,19 @@ function useFetch(...args: UseFetchArgs): UseFetch { const makeFetch = useDeepCallback((method: HTTPMethod): FetchData => { const doFetch = async (routeOrBody?: RouteOrBody, body?: UFBody): Promise => { - if (isServer) return // for now, we don't do anything on the server + if (isServer || path === null) return // for now, we don't do anything on the server controller.current = new AbortController() controller.current.signal.onabort = onAbort const theController = controller.current const { url, options, response } = await doFetchArgs( requestInit, - initialURL, - path, method, theController, cacheLife, cache, + host, + path, routeOrBody, body, interceptors.request @@ -104,11 +103,11 @@ function useFetch(...args: UseFetchArgs): UseFetch { } } - if (!suspense) setLoading(true) - // don't perform the request if there is no more data to fetch (pagination) if (perPage > 0 && !hasMore.current && !error.current) return data.current + if (!suspense) setLoading(true) + const timer = timeout && setTimeout(() => { timedout.current = true theController.abort() @@ -209,7 +208,7 @@ function useFetch(...args: UseFetchArgs): UseFetch { } return doFetch - }, [isServer, onAbort, requestInit, initialURL, path, interceptors, cachePolicy, perPage, timeout, persist, cacheLife, onTimeout, defaults.data, onNewData, forceUpdate, suspense]) + }, [isServer, onAbort, requestInit, host, path, interceptors, cachePolicy, perPage, timeout, persist, cacheLife, onTimeout, defaults.data, onNewData, forceUpdate, suspense]) const post = useCallback(makeFetch(HTTPMethod.POST), [makeFetch]) const del = useCallback(makeFetch(HTTPMethod.DELETE), [makeFetch]) diff --git a/src/useFetchArgs.ts b/src/useFetchArgs.ts index 6200ff51..4a143149 100644 --- a/src/useFetchArgs.ts +++ b/src/useFetchArgs.ts @@ -1,155 +1,98 @@ -import { OptionsMaybeURL, NoUrlOptions, CachePolicies, Interceptors, OverwriteGlobalOptions, Options, RetryOn, RetryDelay, UseFetchArgsReturn, ResponseType, OnError } from './types' +import { Interceptors, OverwriteGlobalOptions, Options, IncomingOptions, UseFetchArgsReturn, CustomOptions } from './types' import { isString, isObject, invariant, pullOutRequestInit, isFunction, isPositiveNumber } from './utils' import { useContext, useMemo } from 'react' import FetchContext from './FetchContext' -import defaults from './defaults' +import defaults, { useFetchArgsDefaults } from './defaults' -const useField = ( - field: keyof OptionsMaybeURL | keyof NoUrlOptions, - urlOrOptions?: string | OptionsMaybeURL, - optionsNoURLs?: NoUrlOptions | any[] -) => { - const context = useContext(FetchContext) - const contextOptions = context.options || {} - return useMemo((): DV => { - if (isObject(urlOrOptions) && field in urlOrOptions) return urlOrOptions[field] - if (isObject(optionsNoURLs) && field in optionsNoURLs) { - return (optionsNoURLs as NoUrlOptions)[field as keyof NoUrlOptions] - } - if (field in contextOptions) return contextOptions[field] - return defaults[field] - }, [urlOrOptions, field, optionsNoURLs, contextOptions]) -} - export default function useFetchArgs( - urlOrOptionsOrOverwriteGlobal?: string | OptionsMaybeURL | OverwriteGlobalOptions, - optionsNoURLsOrOverwriteGlobalOrDeps?: NoUrlOptions | OverwriteGlobalOptions | any[], + urlOrPathOrOptionsOrOverwriteGlobalOptions?: string | IncomingOptions | OverwriteGlobalOptions, + optionsOrOverwriteGlobalOrDeps?: IncomingOptions | OverwriteGlobalOptions | any[], deps?: any[] ): UseFetchArgsReturn { - const context = useContext(FetchContext) - context.options = useMemo(() => { - const overwriteGlobalOptions = (isFunction(urlOrOptionsOrOverwriteGlobal) ? urlOrOptionsOrOverwriteGlobal : isFunction(optionsNoURLsOrOverwriteGlobalOrDeps) && optionsNoURLsOrOverwriteGlobalOrDeps) as OverwriteGlobalOptions - if (!overwriteGlobalOptions) return context.options - // make a copy so we make sure not to modify the original context - return overwriteGlobalOptions({ ...context.options } as Options) - }, [context.options, optionsNoURLsOrOverwriteGlobalOrDeps, urlOrOptionsOrOverwriteGlobal]) - - const urlOrOptions = urlOrOptionsOrOverwriteGlobal as string | OptionsMaybeURL - const optionsNoURLs = optionsNoURLsOrOverwriteGlobalOrDeps as NoUrlOptions - invariant( - !(isObject(urlOrOptions) && isObject(optionsNoURLs)), - 'You cannot have a 2nd parameter of useFetch when your first argument is an object config.' + !(isObject(urlOrPathOrOptionsOrOverwriteGlobalOptions) && isObject(optionsOrOverwriteGlobalOrDeps)), + 'You cannot have a 2nd parameter of useFetch as object when your first argument is an object.' ) + const context = useContext(FetchContext) - const url = useMemo((): string => { - if (isString(urlOrOptions) && urlOrOptions) return urlOrOptions as string - if (isObject(urlOrOptions) && !!urlOrOptions.url) return urlOrOptions.url + const host = ((): string => { + const maybeHost = urlOrPathOrOptionsOrOverwriteGlobalOptions as string + if (isString(maybeHost) && maybeHost.includes('://')) return maybeHost if (context.url) return context.url - return defaults.url - }, [context.url, urlOrOptions]) + return defaults.host + })() + + const path = ((): string | null | undefined => { + const maybePath = urlOrPathOrOptionsOrOverwriteGlobalOptions as string + if (isString(maybePath) && !maybePath.includes('://')) return maybePath + if (maybePath === null) return null + })() + + const overwriteGlobalOptions = useMemo((): OverwriteGlobalOptions | undefined => { + if (isFunction(urlOrPathOrOptionsOrOverwriteGlobalOptions)) return urlOrPathOrOptionsOrOverwriteGlobalOptions as OverwriteGlobalOptions + if (isFunction(optionsOrOverwriteGlobalOrDeps)) return optionsOrOverwriteGlobalOrDeps as OverwriteGlobalOptions + }, []) + + const options = useMemo(() => { + let localOptions = { headers: {} } as IncomingOptions + if (isObject(urlOrPathOrOptionsOrOverwriteGlobalOptions)) { + localOptions = urlOrPathOrOptionsOrOverwriteGlobalOptions as IncomingOptions + } else if (isObject(optionsOrOverwriteGlobalOrDeps)) { + localOptions = optionsOrOverwriteGlobalOrDeps as IncomingOptions + } + let globalOptions = context.options + const finalOptions = { + ...defaults, + ...globalOptions, + ...localOptions, + headers: { + ...defaults.headers, + ...globalOptions.headers, + ...localOptions.headers + } as Headers + } as Options + if (overwriteGlobalOptions) return overwriteGlobalOptions(finalOptions) + return finalOptions + }, []) - invariant( - !!url, - 'The first argument of useFetch is required unless you have a global url setup like: ' - ) + const requestInit = useMemo(() => pullOutRequestInit(options), [options]) const dependencies = useMemo((): any[] | undefined => { - if (Array.isArray(optionsNoURLsOrOverwriteGlobalOrDeps)) return optionsNoURLsOrOverwriteGlobalOrDeps + if (Array.isArray(optionsOrOverwriteGlobalOrDeps)) return optionsOrOverwriteGlobalOrDeps if (Array.isArray(deps)) return deps return defaults.dependencies - }, [optionsNoURLsOrOverwriteGlobalOrDeps, deps]) + }, [optionsOrOverwriteGlobalOrDeps, deps]) - const data = useField('data', urlOrOptions, optionsNoURLs) - const cacheLife = useField('cacheLife', urlOrOptions, optionsNoURLs) + const { cacheLife, retries, retryDelay, retryOn } = options invariant(Number.isInteger(cacheLife) && cacheLife >= 0, '`cacheLife` must be a number >= 0') - const cachePolicy = useField('cachePolicy', urlOrOptions, optionsNoURLs) - const onAbort = useField<() => void>('onAbort', urlOrOptions, optionsNoURLs) - const onError = useField('onError', urlOrOptions, optionsNoURLs) - const onNewData = useField<() => void>('onNewData', urlOrOptions, optionsNoURLs) - const onTimeout = useField<() => void>('onTimeout', urlOrOptions, optionsNoURLs) - const path = useField('path', urlOrOptions, optionsNoURLs) - const perPage = useField('perPage', urlOrOptions, optionsNoURLs) - const persist = useField('persist', urlOrOptions, optionsNoURLs) - const responseType = useField('responseType', urlOrOptions, optionsNoURLs) - const retries = useField('retries', urlOrOptions, optionsNoURLs) invariant(Number.isInteger(retries) && retries >= 0, '`retries` must be a number >= 0') - const retryDelay = useField('retryDelay', urlOrOptions, optionsNoURLs) invariant(isFunction(retryDelay) || Number.isInteger(retryDelay as number) && retryDelay >= 0, '`retryDelay` must be a positive number or a function returning a positive number.') - const retryOn = useField('retryOn', urlOrOptions, optionsNoURLs) const isValidRetryOn = isFunction(retryOn) || (Array.isArray(retryOn) && retryOn.every(isPositiveNumber)) invariant(isValidRetryOn, '`retryOn` must be an array of positive numbers or a function returning a boolean.') - const suspense = useField('suspense', urlOrOptions, optionsNoURLs) - const timeout = useField('timeout', urlOrOptions, optionsNoURLs) - - const loading = useMemo((): boolean => { - if (isObject(urlOrOptions)) return !!urlOrOptions.loading || Array.isArray(dependencies) - if (isObject(optionsNoURLs)) return !!optionsNoURLs.loading || Array.isArray(dependencies) - return defaults.loading || Array.isArray(dependencies) - }, [urlOrOptions, dependencies, optionsNoURLs]) + const loading = options.loading || Array.isArray(dependencies) const interceptors = useMemo((): Interceptors => { - const contextInterceptors = context.options && (context.options.interceptors || {}) - const final: Interceptors = { ...contextInterceptors } - if (isObject(urlOrOptions) && isObject(urlOrOptions.interceptors)) { - if (urlOrOptions.interceptors.request) final.request = urlOrOptions.interceptors.request - if (urlOrOptions.interceptors.response) final.response = urlOrOptions.interceptors.response - } - if (isObject(optionsNoURLs) && isObject(optionsNoURLs.interceptors)) { - if (optionsNoURLs.interceptors.request) final.request = optionsNoURLs.interceptors.request - if (optionsNoURLs.interceptors.response) final.response = optionsNoURLs.interceptors.response - } + const final: Interceptors = {} + if ('request' in options.interceptors) final.request = options.interceptors.request + if ('response' in options.interceptors) final.response = options.interceptors.response return final - }, [context.options, urlOrOptions, optionsNoURLs]) - - const requestInit = useMemo((): RequestInit => { - const contextRequestInit = pullOutRequestInit(context.options as OptionsMaybeURL) + }, [options]) - const requestInitOptions = isObject(urlOrOptions) - ? urlOrOptions - : isObject(optionsNoURLs) - ? optionsNoURLs - : {} - - const requestInit = pullOutRequestInit(requestInitOptions) - - return { - ...contextRequestInit, - ...requestInit, - headers: { - ...defaults.headers, - ...contextRequestInit.headers, - ...requestInit.headers - } - } - }, [context.options, urlOrOptions, optionsNoURLs]) + const customOptions = useMemo((): CustomOptions => { + const customOptionKeys = Object.keys(useFetchArgsDefaults.customOptions) as (keyof CustomOptions)[] // Array + const customOptions = customOptionKeys.reduce((opts, key) => { + (opts as any)[key] = options[key] + return opts + }, {} as CustomOptions) + return { ...customOptions, interceptors, loading } + }, [interceptors, loading]) return { - customOptions: { - cacheLife, - cachePolicy, - interceptors, - onAbort, - onError, - onNewData, - onTimeout, - path, - persist, - perPage, - responseType, - retries, - retryDelay, - retryOn, - suspense, - timeout, - url, - }, + host, + path, + customOptions, requestInit, - defaults: { - data, - loading - }, dependencies } } diff --git a/src/useMutation.ts b/src/useMutation.ts index 36f4fa8d..d461c00b 100644 --- a/src/useMutation.ts +++ b/src/useMutation.ts @@ -1,3 +1,4 @@ +// @ts-nocheck import useFetch, { FetchContext } from '.' import { useContext, useCallback } from 'react' import { ReqBase } from './types' diff --git a/src/useQuery.ts b/src/useQuery.ts index f955be5a..da5e7ff2 100644 --- a/src/useQuery.ts +++ b/src/useQuery.ts @@ -1,3 +1,4 @@ +// @ts-nocheck import useFetch, { FetchContext } from '.' import { useContext, useCallback } from 'react' import { ReqBase, Cache } from './types' diff --git a/src/utils.ts b/src/utils.ts index 6e1c456e..9c8537e6 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,6 +1,6 @@ import { useMemo, useEffect, MutableRefObject, useRef, useCallback, DependencyList } from 'react' import useSSR from 'use-ssr' -import { RequestInitJSON, OptionsMaybeURL, Res, HTTPMethod, ResponseType } from './types' +import { RequestInitJSON, Options, Res, HTTPMethod, ResponseType } from './types' import { FunctionKeys, NonFunctionKeys } from 'utility-types' /** @@ -94,7 +94,7 @@ export const isNumber = (v: any): boolean => Object.prototype.toString.call(v) = * Makes an object that will match the standards of a normal fetch's options * aka: pulls out all useFetch's special options like "onMount" */ -export const pullOutRequestInit = (options?: OptionsMaybeURL): RequestInit => { +export const pullOutRequestInit = (options?: Options): RequestInit => { if (!options) return {} const requestInitFields = [ 'body', From b9b313ce5d3f5f715a785b790ffc04da110e1f7b Mon Sep 17 00:00:00 2001 From: Alex Cory Date: Tue, 21 Apr 2020 00:03:17 -0700 Subject: [PATCH 02/18] cleanup --- src/__tests__/useFetch.test.tsx | 8 ++++---- src/doFetchArgs.ts | 7 ++----- src/types.ts | 20 -------------------- 3 files changed, 6 insertions(+), 29 deletions(-) diff --git a/src/__tests__/useFetch.test.tsx b/src/__tests__/useFetch.test.tsx index 3a882cd0..a0931ca7 100644 --- a/src/__tests__/useFetch.test.tsx +++ b/src/__tests__/useFetch.test.tsx @@ -187,9 +187,9 @@ describe('useFetch - BROWSER - with ', (): void => { fetch.mockResponseOnce(JSON.stringify(expected)) }) - it(`should work correctly: useFetch('/', { data: [] }, [])`, async (): Promise => { + it(`should work correctly: useFetch({ data: [] }, [])`, async (): Promise => { const { result, waitForNextUpdate } = renderHook( - () => useFetch('/', { data: {} }, []), // onMount === true + () => useFetch({ data: {} }, []), // onMount === true { wrapper } ) @@ -202,7 +202,7 @@ describe('useFetch - BROWSER - with ', (): void => { it('should execute GET using Provider url', async (): Promise => { const { result, waitForNextUpdate } = renderHook( - () => useFetch('/', { data: {} }, []), // onMount === true + () => useFetch({ data: {} }, []), // onMount === true { wrapper } ) @@ -320,7 +320,7 @@ describe('timeouts', (): void => { const onAbort = jest.fn() const onTimeout = jest.fn() const { result, waitForNextUpdate } = renderHook( - () => useFetch('/', { + () => useFetch({ timeout, onAbort, onTimeout diff --git a/src/doFetchArgs.ts b/src/doFetchArgs.ts index f2ddd9b6..e4fb9c12 100644 --- a/src/doFetchArgs.ts +++ b/src/doFetchArgs.ts @@ -65,7 +65,7 @@ export default async function doFetchArgs( return headers })() - const options = await (async (route): Promise => { + const options = await (async (): Promise => { const opts: RequestInit = { ...initialOptions, method, @@ -81,14 +81,11 @@ export default async function doFetchArgs( if (body !== null) opts.body = body if (requestInterceptor) { - if (url.includes('/route2')) { - console.log('route: ', route) - } const interceptor = await requestInterceptor({ options: opts, url: host, path, route }) return interceptor as any } return opts - })(route) + })() // console.log('options', options) // TODO: if the body is a file, and this is a large file, it might exceed the size diff --git a/src/types.ts b/src/types.ts index 1bd46486..c0d8a1d8 100644 --- a/src/types.ts +++ b/src/types.ts @@ -173,26 +173,6 @@ export type Cache = { clear: () => void } -// export interface CustomOptionsChill { -// cacheLife?: number -// cachePolicy?: CachePolicies -// data?: any -// interceptors?: Interceptors -// loading?: boolean -// onAbort?: () => void -// onError?: OnError -// onNewData?: (currData: any, newData: any) => any -// onTimeout?: () => void -// persist?: boolean -// perPage?: number -// responseType?: ResponseType -// retries?: number -// retryOn?: RetryOn -// retryDelay?: RetryDelay -// suspense?: boolean -// timeout?: number -// } - export interface CustomOptions { cacheLife: number cachePolicy: CachePolicies From 2caacec838671f33bd104d75f7a9deff8880ae01 Mon Sep 17 00:00:00 2001 From: Alex Cory Date: Tue, 21 Apr 2020 00:20:15 -0700 Subject: [PATCH 03/18] cleanup --- src/__tests__/useFetch.test.tsx | 1 - src/__tests__/useFetchArgs.test.tsx | 12 +++++------- src/doFetchArgs.ts | 1 - src/types.ts | 1 - src/useFetch.ts | 2 +- src/useFetchArgs.ts | 10 +++++----- src/useMutation.ts | 1 - src/useQuery.ts | 1 - 8 files changed, 11 insertions(+), 18 deletions(-) diff --git a/src/__tests__/useFetch.test.tsx b/src/__tests__/useFetch.test.tsx index a0931ca7..171bbe63 100644 --- a/src/__tests__/useFetch.test.tsx +++ b/src/__tests__/useFetch.test.tsx @@ -245,7 +245,6 @@ describe('useFetch - BROWSER - with ', (): void => { ...expected, no: 'way' }) - result.current.cache.clear() }) it('should not make another request when there is no more data `perPage` pagination', async (): Promise => { diff --git a/src/__tests__/useFetchArgs.test.tsx b/src/__tests__/useFetchArgs.test.tsx index 1d865dee..a3dc1062 100644 --- a/src/__tests__/useFetchArgs.test.tsx +++ b/src/__tests__/useFetchArgs.test.tsx @@ -72,19 +72,17 @@ describe('useFetchArgs: general usages', (): void => { it('should set default data === []', (): void => { const { result } = renderHook( - (): any => useFetchArgs('https://cool.com', { - data: [] - }), + (): any => useFetchArgs({ data: [] }), { wrapper } ) expect(result.current).toStrictEqual({ ...useFetchArgsDefaults, - host: 'https://cool.com', + host: 'https://example.com', customOptions: { ...useFetchArgsDefaults.customOptions, loading: false, data: [] - }, + } }) }) @@ -124,7 +122,7 @@ describe('useFetchArgs: general usages', (): void => { ) const { result } = renderHook( - (): any => useFetchArgs('/path'), + (): any => useFetchArgs(), { wrapper: wrapper2 } ) @@ -170,7 +168,7 @@ describe('useFetchArgs: general usages', (): void => { it('should create custom options with `Content-Type: application/text`', (): void => { const options = { headers: { 'Content-Type': 'application/text' } } - const { result } = renderHook((): any => useFetchArgs('https://example.com', options), { wrapper }) + const { result } = renderHook((): any => useFetchArgs(options), { wrapper }) expect(result.current).toStrictEqual({ ...useFetchArgsDefaults, host: 'https://example.com', diff --git a/src/doFetchArgs.ts b/src/doFetchArgs.ts index e4fb9c12..f6b67248 100644 --- a/src/doFetchArgs.ts +++ b/src/doFetchArgs.ts @@ -86,7 +86,6 @@ export default async function doFetchArgs( } return opts })() - // console.log('options', options) // TODO: if the body is a file, and this is a large file, it might exceed the size // limit of the key size. Potential solution: base64 the body diff --git a/src/types.ts b/src/types.ts index c0d8a1d8..adc91b2e 100644 --- a/src/types.ts +++ b/src/types.ts @@ -201,7 +201,6 @@ export type IncomingOptions = Partial & export type Options = CustomOptions & Omit & { body?: BodyInit | object | null } -// TODO: this is still yet to be implemented export type OverwriteGlobalOptions = (options: Options) => Options export type RetryOn = (({ attempt, error, response }: RetryOpts) => Promise) | number[] diff --git a/src/useFetch.ts b/src/useFetch.ts index 02920ec9..dbe47d9b 100644 --- a/src/useFetch.ts +++ b/src/useFetch.ts @@ -43,7 +43,7 @@ function useFetch(...args: UseFetchArgs): UseFetch { retryOn, suspense, timeout, - ...defaults // { data: TData, loading: boolean } + ...defaults } = customOptions const cache = useCache({ persist, cacheLife, cachePolicy }) diff --git a/src/useFetchArgs.ts b/src/useFetchArgs.ts index 4a143149..674e7ebc 100644 --- a/src/useFetchArgs.ts +++ b/src/useFetchArgs.ts @@ -16,18 +16,18 @@ export default function useFetchArgs( ) const context = useContext(FetchContext) - const host = ((): string => { + const host = useMemo((): string => { const maybeHost = urlOrPathOrOptionsOrOverwriteGlobalOptions as string if (isString(maybeHost) && maybeHost.includes('://')) return maybeHost if (context.url) return context.url return defaults.host - })() + }, [context.url, urlOrPathOrOptionsOrOverwriteGlobalOptions]) - const path = ((): string | null | undefined => { + const path = useMemo((): string | null | undefined => { const maybePath = urlOrPathOrOptionsOrOverwriteGlobalOptions as string if (isString(maybePath) && !maybePath.includes('://')) return maybePath if (maybePath === null) return null - })() + }, [urlOrPathOrOptionsOrOverwriteGlobalOptions]) const overwriteGlobalOptions = useMemo((): OverwriteGlobalOptions | undefined => { if (isFunction(urlOrPathOrOptionsOrOverwriteGlobalOptions)) return urlOrPathOrOptionsOrOverwriteGlobalOptions as OverwriteGlobalOptions @@ -54,7 +54,7 @@ export default function useFetchArgs( } as Options if (overwriteGlobalOptions) return overwriteGlobalOptions(finalOptions) return finalOptions - }, []) + }, [urlOrPathOrOptionsOrOverwriteGlobalOptions, overwriteGlobalOptions, context.options]) const requestInit = useMemo(() => pullOutRequestInit(options), [options]) diff --git a/src/useMutation.ts b/src/useMutation.ts index d461c00b..36f4fa8d 100644 --- a/src/useMutation.ts +++ b/src/useMutation.ts @@ -1,4 +1,3 @@ -// @ts-nocheck import useFetch, { FetchContext } from '.' import { useContext, useCallback } from 'react' import { ReqBase } from './types' diff --git a/src/useQuery.ts b/src/useQuery.ts index da5e7ff2..f955be5a 100644 --- a/src/useQuery.ts +++ b/src/useQuery.ts @@ -1,4 +1,3 @@ -// @ts-nocheck import useFetch, { FetchContext } from '.' import { useContext, useCallback } from 'react' import { ReqBase, Cache } from './types' From f32d0292f1394247a6055f31822ef577c2cf6330 Mon Sep 17 00:00:00 2001 From: Alex Cory Date: Tue, 21 Apr 2020 00:58:41 -0700 Subject: [PATCH 04/18] updating documentation --- README.md | 40 ++++++++++++---------------------------- docs/README.md | 41 ++++++++++++----------------------------- src/defaults.ts | 1 + 3 files changed, 25 insertions(+), 57 deletions(-) diff --git a/README.md b/README.md index 2d5c2fce..2f1bbd5c 100644 --- a/README.md +++ b/README.md @@ -147,7 +147,7 @@ function Todos() { -
Basic Usage (auto managed state) useFetch +
Basic Usage (auto-managed state) useFetch This fetch is run `onMount/componentDidMount`. The last argument `[]` means it will run `onMount`. If you pass it a variable like `[someVariable]`, it will run `onMount` and again whenever `someVariable` changes values (aka `onUpdate`). **If no method is specified, GET is the default** @@ -180,16 +180,15 @@ function Todos() {
-
Basic Usage (auto managed state) with Provider +
Basic Usage (auto-managed state) with Provider ```js import useFetch, { Provider } from 'use-http' function Todos() { - const { loading, error, data } = useFetch({ - path: '/todos', - data: [] - }, []) // onMount + const { loading, error, data } = useFetch('/todos', { + data: [] // default for `data` will be an array instead of undefined + }, []) // <- this [] means it will fire onMount return ( <> @@ -215,7 +214,7 @@ const App = () => (
-
Suspense Mode (auto managed state) +
Suspense Mode (auto-managed state) Can put `suspense` in 2 places. Either `useFetch` (A) or `Provider` (B). @@ -223,8 +222,7 @@ Can put `suspense` in 2 places. Either `useFetch` (A) or `Provider` (B). import useFetch, { Provider } from 'use-http' function Todos() { - const { data: todos } = useFetch({ - path: '/todos', + const { data: todos } = useFetch('/todos', { data: [], suspense: true // A. can put `suspense: true` here }, []) // onMount @@ -328,8 +326,7 @@ import useFetch, { Provider } from 'use-http' const Todos = () => { const [page, setPage] = useState(1) - const { data, loading } = useFetch({ - path: `/todos?page=${page}&amountPerPage=15`, + const { data, loading } = useFetch(`/todos?page=${page}&amountPerPage=15`, { onNewData: (currTodos, newTodos) => [...currTodos, ...newTodos], // appends newly fetched todos perPage: 15, // stops making more requests if last todos fetched < 15 data: [] @@ -430,10 +427,7 @@ var {
Relative routes useFetch -⚠️ `baseUrl` is no longer supported, it is now only `url` ```jsx -var request = useFetch({ url: 'https://example.com' }) -// OR var request = useFetch('https://example.com') request.post('/todos', { @@ -451,17 +445,15 @@ request.post('/todos', { ```jsx -const githubRepos = useFetch({ - url: `https://api.github.com/search/repositories?q=` -}) +const { get, abort, loading, data: repos } = useFetch('https://api.github.com/search/repositories?q=') // the line below is not isomorphic, but for simplicity we're using the browsers `encodeURI` -const searchGithubRepos = e => githubRepos.get(encodeURI(e.target.value)) +const searchGithubRepos = e => get(encodeURI(e.target.value)) <> - - {githubRepos.loading ? 'Loading...' : githubRepos.data.items.map(repo => ( + + {loading ? 'Loading...' : repos.data.items.map(repo => (
{repo.name}
))} @@ -853,7 +845,6 @@ This is exactly what you would pass to the normal js `fetch`, with a little extr | `onError` | Runs when the request get's an error. If retrying, it is only called on the last retry attempt. | empty function | | `onNewData` | Merges the current data with the incoming data. Great for pagination. | `(curr, new) => new` | | `onTimeout` | Called when the request times out. | empty function | -| `path` | When using a global `url` set in the `Provider`, this is useful for adding onto it | `''` | | `persist` | Persists data for the duration of `cacheLife`. If `cacheLife` is not set it defaults to 24h. Currently only available in Browser. | `false` | | `perPage` | Stops making more requests if there is no more data to fetch. (i.e. if we have 25 todos, and the perPage is 10, after fetching 2 times, we will have 20 todos. The last 5 tells us we don't have any more to fetch because it's less than 10) For pagination. | `0` | | `responseType` | This will determine how the `data` field is set. If you put `json` then it will try to parse it as JSON. If you set it as an array, it will attempt to parse the `response` in the order of the types you put in the array. Read about why we don't put `formData` in the defaults [in the yellow Note part here](https://developer.mozilla.org/en-US/docs/Web/API/Body/formData). | `['json', 'text', 'blob', 'readableStream']` | @@ -862,7 +853,6 @@ This is exactly what you would pass to the normal js `fetch`, with a little extr | `retryOn` | You can retry on certain http status codes or have custom logic to decide whether to retry or not via a function. Make sure `retries > 0` otherwise it won't retry. | `[]` | | `suspense` | Enables Experimental React Suspense mode. [example](https://codesandbox.io/s/usefetch-suspense-i22wv) | `false` | | `timeout` | The request will be aborted/cancelled after this amount of time. This is also the interval at which `retries` will be made at. **in milliseconds**. If set to `0`, it will not timeout except for browser defaults. | `0` | -| `url` | Allows you to set a base path so relative paths can be used for each request :) | empty string | ```jsx const options = { @@ -906,9 +896,6 @@ const options = { // called when the request times out onTimeout: () => {}, - // if you have a global `url` set up, this is how you can add to it - path: '/path/to/your/api', - // this will tell useFetch not to run the request if the list doesn't haveMore. (pagination) // i.e. if the last page fetched was < 15, don't run the request again perPage: 15, @@ -957,9 +944,6 @@ const options = { // amount of time before the request get's canceled/aborted timeout: 10000, - - // used to be `baseUrl`. You can set your URL this way instead of as the 1st argument - url: 'https://example.com', } useFetch(options) diff --git a/docs/README.md b/docs/README.md index cd21021c..61744339 100644 --- a/docs/README.md +++ b/docs/README.md @@ -115,7 +115,7 @@ yarn add use-http or npm i -S use-http Usage ============= -Basic Usage (auto managed state) +Basic Usage (auto-managed state) ------------------- This fetch is run `onMount/componentDidMount`. The last argument `[]` means it will run `onMount`. If you pass it a variable like `[someVariable]`, it will run `onMount` and again whenever `someVariable` changes values (aka `onUpdate`). **If no method is specified, GET is the default** @@ -190,17 +190,16 @@ function Todos() { -Basic Usage With Provider (auto managed state) +Basic Usage With Provider (auto-managed state) --------------------------------------------- ```js import useFetch, { Provider } from 'use-http' function Todos() { - const { loading, error, data } = useFetch({ - path: '/todos', - data: [] // default for `data` will be an array instead of undefined - }, []) // onMount + const { loading, error, data } = useFetch('/todos', { + data: [] // default for `data` will be an array instead of undefined + }, []) // <- this [] means it will fire onMount return ( <> @@ -225,15 +224,14 @@ const App = () => ( -Suspense Mode (auto managed state) +Suspense Mode (auto-managed state) ---------------------------------- ```js import useFetch, { Provider } from 'use-http' function Todos() { - const { data: todos } = useFetch({ - path: '/todos', + const { data: todos } = useFetch('/todos', { data: [], suspense: true // can put it in 2 places. Here or in Provider }, []) // onMount @@ -309,8 +307,7 @@ import useFetch, { Provider } from 'use-http' const Todos = () => { const [page, setPage] = useState(1) - const { data, loading } = useFetch({ - path: `/todos?page=${page}&amountPerPage=15`, + const { data, loading } = useFetch(`/todos?page=${page}&amountPerPage=15`, { onNewData: (currTodos, newTodos) => [...currTodos, ...newTodos], // appends newly fetched todos perPage: 15, // stops making more requests if last todos fetched < 15 data: [] @@ -407,11 +404,7 @@ var { Relative routes --------------- -⚠️ `baseUrl` is no longer supported, it is now only `url` - ```js -var request = useFetch({ url: 'https://example.com' }) -// OR var request = useFetch('https://example.com') request.post('/todos', { @@ -427,17 +420,15 @@ Abort ```js -const githubRepos = useFetch({ - url: `https://api.github.com/search/repositories?q=` -}) +const { get, abort, loading, data: repos } = useFetch('https://api.github.com/search/repositories?q=') // the line below is not isomorphic, but for simplicity we're using the browsers `encodeURI` -const searchGithubRepos = e => githubRepos.get(encodeURI(e.target.value)) +const searchGithubRepos = e => get(encodeURI(e.target.value)) <> - - {githubRepos.loading ? 'Loading...' : githubRepos.data.items.map(repo => ( + + {loading ? 'Loading...' : repos.data.items.map(repo => (
{repo.name}
))} @@ -804,7 +795,6 @@ This is exactly what you would pass to the normal js `fetch`, with a little extr | `onError` | Runs when the request get's an error. If retrying, it is only called on the last retry attempt. | empty function | | `onNewData` | Merges the current data with the incoming data. Great for pagination. | `(curr, new) => new` | | `onTimeout` | Called when the request times out. | empty function | -| `path` | When using a global `url` set in the `Provider`, this is useful for adding onto it | `''` | | `persist` | Persists data for the duration of `cacheLife`. If `cacheLife` is not set it defaults to 24h. Currently only available in Browser. | `false` | | `responseType` | This will determine how the `data` field is set. If you put `json` then it will try to parse it as JSON. If you set it as an array, it will attempt to parse the `response` in the order of the types you put in the array. Read about why we don't put `formData` in the defaults [in the yellow Note part here](https://developer.mozilla.org/en-US/docs/Web/API/Body/formData). | `['json', 'text', 'blob', 'readableStream']` | | `perPage` | Stops making more requests if there is no more data to fetch. (i.e. if we have 25 todos, and the perPage is 10, after fetching 2 times, we will have 20 todos. The last 5 tells us we don't have any more to fetch because it's less than 10) For pagination. | `0` | @@ -813,7 +803,6 @@ This is exactly what you would pass to the normal js `fetch`, with a little extr | `retryOn` | You can retry on certain http status codes or have custom logic to decide whether to retry or not via a function. Make sure `retries > 0` otherwise it won't retry. | `[]` | | `suspense` | Enables Experimental React Suspense mode. [example](https://codesandbox.io/s/usefetch-suspense-i22wv) | `false` | | `timeout` | The request will be aborted/cancelled after this amount of time. This is also the interval at which `retries` will be made at. **in milliseconds**. If set to `0`, it will not timeout except for browser defaults. | `0` | -| `url` | Allows you to set a base path so relative paths can be used for each request :) | empty string | ```jsx const options = { @@ -857,9 +846,6 @@ const options = { // called when the request times out onTimeout: () => {}, - // if you have a global `url` set up, this is how you can add to it - path: '/path/to/your/api', - // this will tell useFetch not to run the request if the list doesn't haveMore. (pagination) // i.e. if the last page fetched was < 15, don't run the request again perPage: 15, @@ -909,9 +895,6 @@ const options = { // amount of time before the request get's canceled/aborted timeout: 10000, - - // used to be `baseUrl`. You can set your URL this way instead of as the 1st argument - url: 'https://example.com', } useFetch(options) diff --git a/src/defaults.ts b/src/defaults.ts index d6ae8e7c..634d377b 100644 --- a/src/defaults.ts +++ b/src/defaults.ts @@ -21,6 +21,7 @@ export const useFetchArgsDefaults: UseFetchArgsReturn = { retryOn: [], suspense: false, timeout: 0, + // defaults data: undefined, loading: false }, From 78d27f142d3b4b6cec46699a54c112a2fd1a9f3f Mon Sep 17 00:00:00 2001 From: Alex Cory Date: Tue, 21 Apr 2020 01:17:48 -0700 Subject: [PATCH 05/18] added docs for conditional auto-managed state --- README.md | 27 ++++++++++++--------------- docs/README.md | 29 ++++++++++++++--------------- 2 files changed, 26 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index 2f1bbd5c..e9cd58d9 100644 --- a/README.md +++ b/README.md @@ -155,12 +155,10 @@ This fetch is run `onMount/componentDidMount`. The last argument `[]` means it w import useFetch from 'use-http' function Todos() { - // accepts all `fetch` options - const options = { - data: [], // setting default for `data` as array instead of undefined - } - - const { loading, error, data } = useFetch('https://example.com/todos', options, []) // onMount (GET by default) + const { loading, error, data } = useFetch('https://example.com/todos', { + // these options accept all native `fetch` options + data: [] // defaults the `data` to an array instead of `undefined` + }, []) // <- this [] means it will fire onMount (GET by default) return ( <> @@ -180,23 +178,22 @@ function Todos() {
-
Basic Usage (auto-managed state) with Provider +
Conditional (auto-managed state) with Provider +For conditional fetching via auto-managed state, if you don't want `useFetch` to execute, you must pass `null`. Any other value will not block it from executing. This would execute whenever the `id` changes and whenever the `id` exists. ```js import useFetch, { Provider } from 'use-http' -function Todos() { - const { loading, error, data } = useFetch('/todos', { - data: [] // default for `data` will be an array instead of undefined - }, []) // <- this [] means it will fire onMount - +function Todo({ id }) { + const url = id ? `/todos/${id}` : null + const { loading, error, data: todo } = useFetch(url, { + data: { title: '' } + }, [id]) return ( <> {error && 'Error!'} {loading && 'Loading...'} - {data.map(todo => ( -
{todo.title}
- )} + {todo.title} ) } diff --git a/docs/README.md b/docs/README.md index 61744339..9d7fe319 100644 --- a/docs/README.md +++ b/docs/README.md @@ -115,7 +115,7 @@ yarn add use-http or npm i -S use-http Usage ============= -Basic Usage (auto-managed state) +Basic Usage Auto-Managed State ------------------- This fetch is run `onMount/componentDidMount`. The last argument `[]` means it will run `onMount`. If you pass it a variable like `[someVariable]`, it will run `onMount` and again whenever `someVariable` changes values (aka `onUpdate`). **If no method is specified, GET is the default** @@ -124,11 +124,10 @@ This fetch is run `onMount/componentDidMount`. The last argument `[]` means it w import useFetch from 'use-http' function Todos() { - const options = { // accepts all `fetch` options - data: [] // default for `data` will be an array instead of undefined - } - - const { loading, error, data } = useFetch('https://example.com/todos', options, []) // onMount (GET by default) + const { loading, error, data } = useFetch('https://example.com/todos', { + // these options accept all native `fetch` options + data: [] // defaults the `data` to an array instead of `undefined` + }, []) // <- this [] means it will fire onMount (GET by default) return ( <> @@ -190,24 +189,24 @@ function Todos() { -Basic Usage With Provider (auto-managed state) +Conditional Auto-Managed State With Provider --------------------------------------------- +For conditional fetching via auto-managed state, if you don't want `useFetch` to execute, you must pass `null`. Any other value will not block it from executing. This would execute whenever the `id` changes and whenever the `id` exists. + ```js import useFetch, { Provider } from 'use-http' -function Todos() { - const { loading, error, data } = useFetch('/todos', { - data: [] // default for `data` will be an array instead of undefined - }, []) // <- this [] means it will fire onMount - +function Todo({ id }) { + const url = id ? `/todos/${id}` : null + const { loading, error, data } = useFetch(url, { + data: { title: '' } + }, [id]) return ( <> {error && 'Error!'} {loading && 'Loading...'} - {data.map(todo => ( -
{todo.title}
- )} + {todo.title} ) } From 3bc5109e52cf5f9de62c1c6585892ad20a265b72 Mon Sep 17 00:00:00 2001 From: Alex Cory Date: Tue, 21 Apr 2020 01:29:13 -0700 Subject: [PATCH 06/18] cleanup --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index e9cd58d9..97fec893 100644 --- a/README.md +++ b/README.md @@ -179,6 +179,7 @@ function Todos() {
Conditional (auto-managed state) with Provider + For conditional fetching via auto-managed state, if you don't want `useFetch` to execute, you must pass `null`. Any other value will not block it from executing. This would execute whenever the `id` changes and whenever the `id` exists. ```js From 5af7bbdc8a2f990fca9ace5b3ffe7225a1db7012 Mon Sep 17 00:00:00 2001 From: Alex Cory Date: Tue, 21 Apr 2020 01:43:55 -0700 Subject: [PATCH 07/18] docs cleanup --- README.md | 16 ++++++++-------- docs/README.md | 8 ++++---- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 97fec893..67d53eaf 100644 --- a/README.md +++ b/README.md @@ -101,7 +101,7 @@ Usage - useFetch - Next.js [![](https://img.shields.io/badge/example-blue.svg)](https://codesandbox.io/s/usefetch-in-nextjs-nn9fm) - useFetch - create-react-app [![](https://img.shields.io/badge/example-blue.svg)](https://codesandbox.io/embed/km04k9k9x5) -
Basic Usage (managed state) useFetch +
Basic Usage Managed State useFetch If the last argument of `useFetch` is not a dependency array `[]`, then it will not fire until you call one of the http methods like `get`, `post`, etc. @@ -147,7 +147,7 @@ function Todos() {
-
Basic Usage (auto-managed state) useFetch +
Basic Usage Auto-Managed State useFetch This fetch is run `onMount/componentDidMount`. The last argument `[]` means it will run `onMount`. If you pass it a variable like `[someVariable]`, it will run `onMount` and again whenever `someVariable` changes values (aka `onUpdate`). **If no method is specified, GET is the default** @@ -178,7 +178,7 @@ function Todos() {
-
Conditional (auto-managed state) with Provider +
Conditional Auto-Managed State with Provider For conditional fetching via auto-managed state, if you don't want `useFetch` to execute, you must pass `null`. Any other value will not block it from executing. This would execute whenever the `id` changes and whenever the `id` exists. @@ -186,8 +186,8 @@ For conditional fetching via auto-managed state, if you don't want `useFetch` to import useFetch, { Provider } from 'use-http' function Todo({ id }) { - const url = id ? `/todos/${id}` : null - const { loading, error, data: todo } = useFetch(url, { + const path = id ? `/todos/${id}` : null + const { loading, error, data: todo } = useFetch(path, { data: { title: '' } }, [id]) return ( @@ -212,7 +212,7 @@ const App = () => (
-
Suspense Mode (auto-managed state) +
Suspense Mode(experimental) Auto-Managed State Can put `suspense` in 2 places. Either `useFetch` (A) or `Provider` (B). @@ -246,9 +246,9 @@ function App() {
-
Suspense Mode (managed state) +
Suspense Mode(experimental) Managed State -Can put `suspense` in 2 places. Either `useFetch` (A) or `Provider` (B). +Can put `suspense` in 2 places. Either `useFetch` (A) or `Provider` (B). Suspense mode via managed state is very experimental. ```js import useFetch, { Provider } from 'use-http' diff --git a/docs/README.md b/docs/README.md index 9d7fe319..a68bf84e 100644 --- a/docs/README.md +++ b/docs/README.md @@ -198,8 +198,8 @@ For conditional fetching via auto-managed state, if you don't want `useFetch` to import useFetch, { Provider } from 'use-http' function Todo({ id }) { - const url = id ? `/todos/${id}` : null - const { loading, error, data } = useFetch(url, { + const path = id ? `/todos/${id}` : null + const { loading, error, data } = useFetch(path, { data: { title: '' } }, [id]) return ( @@ -223,7 +223,7 @@ const App = () => ( -Suspense Mode (auto-managed state) +Suspense Mode Auto-Managed State ---------------------------------- ```js @@ -254,7 +254,7 @@ function App() { -Suspense Mode (managed state) +Suspense Mode Managed State ----------------------------- Can put `suspense` in 2 places. Either `useFetch` (A) or `Provider` (B). From 9594e9ecf43a9ee24af9f79b056230c8dc975c6c Mon Sep 17 00:00:00 2001 From: Alex Cory Date: Tue, 21 Apr 2020 01:45:44 -0700 Subject: [PATCH 08/18] cleanup --- README.md | 2 +- docs/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 67d53eaf..a296c1ac 100644 --- a/README.md +++ b/README.md @@ -149,7 +149,7 @@ function Todos() {
Basic Usage Auto-Managed State useFetch -This fetch is run `onMount/componentDidMount`. The last argument `[]` means it will run `onMount`. If you pass it a variable like `[someVariable]`, it will run `onMount` and again whenever `someVariable` changes values (aka `onUpdate`). **If no method is specified, GET is the default** +This fetch is run `onMount/componentDidMount`. The last argument `[]` means it will run `onMount`. If you pass it a variable like `[someVariable]`, it will run `onMount` and again whenever `someVariable` changes values (aka `onUpdate`). If no method is specified, GET is the default. ```js import useFetch from 'use-http' diff --git a/docs/README.md b/docs/README.md index a68bf84e..f5660755 100644 --- a/docs/README.md +++ b/docs/README.md @@ -118,7 +118,7 @@ Usage Basic Usage Auto-Managed State ------------------- -This fetch is run `onMount/componentDidMount`. The last argument `[]` means it will run `onMount`. If you pass it a variable like `[someVariable]`, it will run `onMount` and again whenever `someVariable` changes values (aka `onUpdate`). **If no method is specified, GET is the default** +This fetch is run `onMount/componentDidMount`. The last argument `[]` means it will run `onMount`. If you pass it a variable like `[someVariable]`, it will run `onMount` and again whenever `someVariable` changes values (aka `onUpdate`). If no method is specified, GET is the default. ```js import useFetch from 'use-http' From ca159c07ce2f4684d5062d9734b428ed6d3d3ca8 Mon Sep 17 00:00:00 2001 From: Alex Cory Date: Tue, 21 Apr 2020 01:51:12 -0700 Subject: [PATCH 09/18] link to contributions.md --- README.md | 2 +- docs/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a296c1ac..e2fbbd5f 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@

- + diff --git a/docs/README.md b/docs/README.md index f5660755..ec41e497 100644 --- a/docs/README.md +++ b/docs/README.md @@ -5,7 +5,7 @@

🐶 React hook for making isomorphic http requests

- + From 8e5c6bec4f09179488ff3976268a6395d964aec1 Mon Sep 17 00:00:00 2001 From: Alex Cory Date: Tue, 21 Apr 2020 02:09:16 -0700 Subject: [PATCH 10/18] bettering docs --- README.md | 5 ++--- docs/README.md | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index e2fbbd5f..c9e2b6e5 100644 --- a/README.md +++ b/README.md @@ -187,9 +187,8 @@ import useFetch, { Provider } from 'use-http' function Todo({ id }) { const path = id ? `/todos/${id}` : null - const { loading, error, data: todo } = useFetch(path, { - data: { title: '' } - }, [id]) + const defaultTodo = { title: 'default title' } + const { loading, error, data } = useFetch(path, { data: defaultTodo }, [id]) return ( <> {error && 'Error!'} diff --git a/docs/README.md b/docs/README.md index ec41e497..941f21ed 100644 --- a/docs/README.md +++ b/docs/README.md @@ -199,9 +199,8 @@ import useFetch, { Provider } from 'use-http' function Todo({ id }) { const path = id ? `/todos/${id}` : null - const { loading, error, data } = useFetch(path, { - data: { title: '' } - }, [id]) + const defaultTodo = { title: 'default title' } + const { loading, error, data } = useFetch(path, { data: defaultTodo }, [id]) return ( <> {error && 'Error!'} From 8257ce0473ce9f874c9426a0814478be6d1f269c Mon Sep 17 00:00:00 2001 From: Alex Cory Date: Tue, 21 Apr 2020 15:43:00 -0700 Subject: [PATCH 11/18] added `skip` option, `skip` tests, fixed bug with `responseType` --- README.md | 4 ++++ docs/README.md | 12 +++++++++--- src/__tests__/useFetch.test.tsx | 30 ++++++++++++++++++++++++++++++ src/defaults.ts | 1 + src/types.ts | 4 +++- src/useFetch.ts | 3 ++- src/useFetchArgs.ts | 7 +++---- src/utils.ts | 8 ++++---- 8 files changed, 56 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index c9e2b6e5..6fe7e891 100644 --- a/README.md +++ b/README.md @@ -848,6 +848,7 @@ This is exactly what you would pass to the normal js `fetch`, with a little extr | `retries` | When a request fails or times out, retry the request this many times. By default it will not retry. | `0` | | `retryDelay` | You can retry with certain intervals i.e. 30 seconds `30000` or with custom logic (i.e. to increase retry intervals). | `1000` | | `retryOn` | You can retry on certain http status codes or have custom logic to decide whether to retry or not via a function. Make sure `retries > 0` otherwise it won't retry. | `[]` | +| `skip` | Tells `useFetch` not to execute if set to `true` | `false` | | `suspense` | Enables Experimental React Suspense mode. [example](https://codesandbox.io/s/usefetch-suspense-i22wv) | `false` | | `timeout` | The request will be aborted/cancelled after this amount of time. This is also the interval at which `retries` will be made at. **in milliseconds**. If set to `0`, it will not timeout except for browser defaults. | `0` | @@ -936,6 +937,9 @@ const options = { } }, + // tells useFetch not to execute if set to true + skip: false, + // enables experimental React Suspense mode suspense: true, // defaults to `false` diff --git a/docs/README.md b/docs/README.md index 941f21ed..9c3ae690 100644 --- a/docs/README.md +++ b/docs/README.md @@ -192,15 +192,17 @@ function Todos() { Conditional Auto-Managed State With Provider --------------------------------------------- -For conditional fetching via auto-managed state, if you don't want `useFetch` to execute, you must pass `null`. Any other value will not block it from executing. This would execute whenever the `id` changes and whenever the `id` exists. +For conditional fetching via auto-managed state, if you don't want `useFetch` to execute, one way is to set `skip: true`. This would execute whenever the `id` changes and whenever the `id` exists. ```js import useFetch, { Provider } from 'use-http' function Todo({ id }) { - const path = id ? `/todos/${id}` : null const defaultTodo = { title: 'default title' } - const { loading, error, data } = useFetch(path, { data: defaultTodo }, [id]) + const { loading, error, data: todo } = useFetch(`/todos/${id}`, { + data: defaultTodo, + skip: !id + }, [id]) return ( <> {error && 'Error!'} @@ -799,6 +801,7 @@ This is exactly what you would pass to the normal js `fetch`, with a little extr | `retries` | When a request fails or times out, retry the request this many times. By default it will not retry. | `0` | | `retryDelay` | You can retry with certain intervals i.e. 30 seconds `30000` or with custom logic (i.e. to increase retry intervals). | `1000` | | `retryOn` | You can retry on certain http status codes or have custom logic to decide whether to retry or not via a function. Make sure `retries > 0` otherwise it won't retry. | `[]` | +| `skip` | Tells `useFetch` not to execute if set to `true` | `false` | | `suspense` | Enables Experimental React Suspense mode. [example](https://codesandbox.io/s/usefetch-suspense-i22wv) | `false` | | `timeout` | The request will be aborted/cancelled after this amount of time. This is also the interval at which `retries` will be made at. **in milliseconds**. If set to `0`, it will not timeout except for browser defaults. | `0` | @@ -888,6 +891,9 @@ const options = { } }, + // tells useFetch not to execute if set to true + skip: false, + // enables experimental React Suspense mode suspense: true, // defaults to `false` diff --git a/src/__tests__/useFetch.test.tsx b/src/__tests__/useFetch.test.tsx index 171bbe63..ebd25398 100644 --- a/src/__tests__/useFetch.test.tsx +++ b/src/__tests__/useFetch.test.tsx @@ -132,6 +132,36 @@ describe('useFetch - BROWSER - basic functionality', (): void => { }) }) +describe('useFetch - auto-managed state', (): void => { + afterEach((): void => { + cleanup() + fetch.resetMocks() + }) + + it('should not `skip` fetch execution when false', async (): Promise => { + fetch.resetMocks() + fetch.mockResponseOnce('Alex Cory') + const { result, waitForNextUpdate } = renderHook( + () => useFetch('/path', { skip: false }, []), // onMount === true + ) + expect(result.current.data).toEqual(undefined) + expect(result.current.loading).toBe(true) + await waitForNextUpdate() + expect(result.current.data).toEqual('Alex Cory') + expect(result.current.loading).toBe(false) + expect(fetch.mock.calls.length).toBe(1) + }) + + it('should `skip` fetch execution when true', async (): Promise => { + const { result } = renderHook( + () => useFetch('/path', { skip: true }, []), // onMount === true + ) + expect(result.current.data).toEqual(undefined) + expect(result.current.loading).toBe(false) + expect(fetch.mock.calls.length).toBe(0) + }) +}) + describe('useFetch - responseType', (): void => { afterEach((): void => { cleanup() diff --git a/src/defaults.ts b/src/defaults.ts index 634d377b..ca7bd128 100644 --- a/src/defaults.ts +++ b/src/defaults.ts @@ -19,6 +19,7 @@ export const useFetchArgsDefaults: UseFetchArgsReturn = { retries: 0, retryDelay: 1000, retryOn: [], + skip: false, suspense: false, timeout: 0, // defaults diff --git a/src/types.ts b/src/types.ts index adc91b2e..c0536dfa 100644 --- a/src/types.ts +++ b/src/types.ts @@ -189,6 +189,7 @@ export interface CustomOptions { retries: number retryOn: RetryOn retryDelay: RetryDelay + skip: boolean suspense: boolean timeout: number } @@ -213,7 +214,7 @@ export type OnError = ({ error }: { error: Error }) => void export type UseFetchArgsReturn = { host: string - path?: string | null + path?: string customOptions: { cacheLife: number cachePolicy: CachePolicies @@ -228,6 +229,7 @@ export type UseFetchArgsReturn = { retries: number retryDelay: RetryDelay retryOn: RetryOn | undefined + skip: boolean suspense: boolean timeout: number // defaults diff --git a/src/useFetch.ts b/src/useFetch.ts index dbe47d9b..0e2c49fb 100644 --- a/src/useFetch.ts +++ b/src/useFetch.ts @@ -41,6 +41,7 @@ function useFetch(...args: UseFetchArgs): UseFetch { retries, retryDelay, retryOn, + skip, suspense, timeout, ...defaults @@ -67,7 +68,7 @@ function useFetch(...args: UseFetchArgs): UseFetch { const makeFetch = useDeepCallback((method: HTTPMethod): FetchData => { const doFetch = async (routeOrBody?: RouteOrBody, body?: UFBody): Promise => { - if (isServer || path === null) return // for now, we don't do anything on the server + if (isServer || skip) return // for now, we don't do anything on the server controller.current = new AbortController() controller.current.signal.onabort = onAbort const theController = controller.current diff --git a/src/useFetchArgs.ts b/src/useFetchArgs.ts index 674e7ebc..94bd820a 100644 --- a/src/useFetchArgs.ts +++ b/src/useFetchArgs.ts @@ -23,10 +23,9 @@ export default function useFetchArgs( return defaults.host }, [context.url, urlOrPathOrOptionsOrOverwriteGlobalOptions]) - const path = useMemo((): string | null | undefined => { + const path = useMemo((): string | undefined => { const maybePath = urlOrPathOrOptionsOrOverwriteGlobalOptions as string if (isString(maybePath) && !maybePath.includes('://')) return maybePath - if (maybePath === null) return null }, [urlOrPathOrOptionsOrOverwriteGlobalOptions]) const overwriteGlobalOptions = useMemo((): OverwriteGlobalOptions | undefined => { @@ -64,13 +63,13 @@ export default function useFetchArgs( return defaults.dependencies }, [optionsOrOverwriteGlobalOrDeps, deps]) - const { cacheLife, retries, retryDelay, retryOn } = options + const { cacheLife, retries, retryDelay, retryOn, skip } = options invariant(Number.isInteger(cacheLife) && cacheLife >= 0, '`cacheLife` must be a number >= 0') invariant(Number.isInteger(retries) && retries >= 0, '`retries` must be a number >= 0') invariant(isFunction(retryDelay) || Number.isInteger(retryDelay as number) && retryDelay >= 0, '`retryDelay` must be a positive number or a function returning a positive number.') const isValidRetryOn = isFunction(retryOn) || (Array.isArray(retryOn) && retryOn.every(isPositiveNumber)) invariant(isValidRetryOn, '`retryOn` must be an array of positive numbers or a function returning a boolean.') - const loading = options.loading || Array.isArray(dependencies) + const loading = (options.loading || Array.isArray(dependencies)) && !skip const interceptors = useMemo((): Interceptors => { const final: Interceptors = {} diff --git a/src/utils.ts b/src/utils.ts index 9c8537e6..1b1a5372 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -153,12 +153,12 @@ export const tryGetData = async (res: Response | undefined, defaultData: any, re return !isEmpty(defaultData) && isEmpty(data) ? defaultData : data } -const tryRetry = async (res: Response, types: ResponseType): Promise => { +const tryRetry = async (res: Response, types: ResponseType, i: number = 0): Promise => { try { - return (res.clone() as any)[types[0]]() + return await (res.clone() as any)[types[i]]() } catch (error) { - if (types.length === 1) throw error - return tryRetry(res.clone(), (types as any).slice(1)) + if (types.length - 1 === i) throw error + return tryRetry(res.clone(), types, ++i) } } From 9cd379d6dbc46e61dacad05ecd7cb0abe1529cff Mon Sep 17 00:00:00 2001 From: Alex Cory Date: Tue, 21 Apr 2020 15:44:18 -0700 Subject: [PATCH 12/18] forgot to hit save --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 6fe7e891..51982cf6 100644 --- a/README.md +++ b/README.md @@ -180,15 +180,17 @@ function Todos() {

Conditional Auto-Managed State with Provider -For conditional fetching via auto-managed state, if you don't want `useFetch` to execute, you must pass `null`. Any other value will not block it from executing. This would execute whenever the `id` changes and whenever the `id` exists. +For conditional fetching via auto-managed state, if you don't want `useFetch` to execute, one way is to set `skip: true`. This would execute whenever the `id` changes and whenever the `id` exists. ```js import useFetch, { Provider } from 'use-http' function Todo({ id }) { - const path = id ? `/todos/${id}` : null const defaultTodo = { title: 'default title' } - const { loading, error, data } = useFetch(path, { data: defaultTodo }, [id]) + const { loading, error, data: todo } = useFetch(`/todos/${id}`, { + data: defaultTodo, + skip: !id + }, [id]) return ( <> {error && 'Error!'} From 51c6d0367c45640f3427039226a7e2ebd8be2ace Mon Sep 17 00:00:00 2001 From: Alex Cory Date: Tue, 21 Apr 2020 16:31:19 -0700 Subject: [PATCH 13/18] tests for not overwriting every instance of useFetch global options --- src/__tests__/useFetch.test.tsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/__tests__/useFetch.test.tsx b/src/__tests__/useFetch.test.tsx index ebd25398..30163f6e 100644 --- a/src/__tests__/useFetch.test.tsx +++ b/src/__tests__/useFetch.test.tsx @@ -697,7 +697,7 @@ describe('useFetch - BROWSER - Overwrite Global Options set in Provider', (): vo }) beforeEach((): void => { - fetch.mockResponseOnce(JSON.stringify({})) + fetch.mockResponse(JSON.stringify({})) }) it('should only add Content-Type: application/json for POST and PUT by default', async (): Promise => { @@ -757,7 +757,7 @@ describe('useFetch - BROWSER - Overwrite Global Options set in Provider', (): vo expect(fetch).toHaveBeenCalledTimes(1) }) - it('should overwrite options set in the Provider', async (): Promise => { + it('should overwrite options set in the Provider and not every instance of useFetch', async (): Promise => { const expectedHeaders = defaults.headers const { result, waitForNextUpdate } = renderHook( () => useFetch(globalOptions => { @@ -773,6 +773,14 @@ describe('useFetch - BROWSER - Overwrite Global Options set in Provider', (): vo expect(fetch.mock.calls[0][0]).toBe('https://example.com') expect((fetch.mock.calls[0][1] as any).headers).toEqual(expectedHeaders) expect(fetch).toHaveBeenCalledTimes(1) + const expectedHeadersGET = { ...defaults.headers, ...providerHeaders } + const { waitForNextUpdate: wait2 } = renderHook( + () => useFetch('/', []), // onMount === true + { wrapper } + ) + await wait2() + expect((fetch.mock.calls[1][1] as any).headers).toEqual(expectedHeadersGET) + expect(fetch).toHaveBeenCalledTimes(2) }) }) From 249db155bce177ce95682611eca99e7b2114a8ef Mon Sep 17 00:00:00 2001 From: Alex Cory Date: Tue, 21 Apr 2020 17:59:08 -0700 Subject: [PATCH 14/18] tests for path and route without a / --- src/__tests__/useFetch.test.tsx | 18 +++++++++++++++++- src/doFetchArgs.ts | 4 ++-- src/utils.ts | 26 ++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 3 deletions(-) diff --git a/src/__tests__/useFetch.test.tsx b/src/__tests__/useFetch.test.tsx index 30163f6e..4eb10ed6 100644 --- a/src/__tests__/useFetch.test.tsx +++ b/src/__tests__/useFetch.test.tsx @@ -13,7 +13,7 @@ import * as mockdate from 'mockdate' import defaults from '../defaults' import { Res, IncomingOptions, CachePolicies } from '../types' -import { emptyCustomResponse, sleep, makeError } from '../utils' +import { emptyCustomResponse, sleep, makeError, addSlash } from '../utils' const fetch = global.fetch as FetchMock @@ -132,6 +132,22 @@ describe('useFetch - BROWSER - basic functionality', (): void => { }) }) +describe('useFetch - handling host/path/route parsing properly', (): void => { + it ('should have addSlash run properly', (): void => { + expect(addSlash('', '')).toBe('') + expect(addSlash('')).toBe('') + expect(addSlash('?foo=bar', 'a.com')).toBe('?foo=bar') + expect(addSlash('?foo=bar', 'a.com/')).toBe('?foo=bar') + expect(addSlash('?foo=bar')).toBe('?foo=bar') + expect(addSlash('/foo', 'a.com')).toBe('/foo') + expect(addSlash('/foo', 'a.com/')).toBe('foo') + expect(addSlash('foo', 'a.com')).toBe('/foo') + expect(addSlash('foo', 'a.com/')).toBe('foo') + expect(addSlash('foo')).toBe('/foo') + expect(addSlash('/foo')).toBe('/foo') + }) +}) + describe('useFetch - auto-managed state', (): void => { afterEach((): void => { cleanup() diff --git a/src/doFetchArgs.ts b/src/doFetchArgs.ts index f6b67248..8711ba66 100644 --- a/src/doFetchArgs.ts +++ b/src/doFetchArgs.ts @@ -1,5 +1,5 @@ import { HTTPMethod, Interceptors, ValueOf, DoFetchArgs, Cache } from './types' -import { invariant, isServer, isString, isBodyObject } from './utils' +import { invariant, isServer, isString, isBodyObject, addSlash } from './utils' const { GET } = HTTPMethod @@ -34,7 +34,7 @@ export default async function doFetchArgs( return '' })() - const url = `${host}${path ?? ''}${route ?? ''}` + const url = `${host}${addSlash(path, host)}${addSlash(route)}` const body = ((): BodyInit | null => { if (isBodyObject(routeOrBody)) return JSON.stringify(routeOrBody) diff --git a/src/utils.ts b/src/utils.ts index 1b1a5372..b9c45511 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -247,3 +247,29 @@ export const makeError = (name: string | number, message: string) => { error.name = name + '' return error } + +/** + * Determines if we need to add a slash to front + * of a path, and adds it if we do. + * Cases: + * (path = '', url = '' || null | undefined) => '' + * (path = '?foo=bar', url = 'a.com') => '?foo=bar' + * (path = '?foo=bar', url = 'a.com/') => '?foo=bar' + * (path = 'foo', url = 'a.com') => '/foo' + * (path = 'foo', url = 'a.com/') => 'foo' + * (path = '/foo', url = 'a.com') => '/foo' + * (path = '/foo', url = 'a.com/') => 'foo' + * (path = '?foo=bar') => '?foo=bar' + * (path = 'foo') => '/foo' + * (path = '/foo') => '/foo' + */ +export const addSlash = (input?: string, url?: string) => { + if (!input) return '' + if (!url) { + if (input.startsWith('?') || input.startsWith('/')) return input + return `/${input}` + } + if (url.endsWith('/') && input.startsWith('/')) return input.substr(1) + if (!url.endsWith('/') && !input.startsWith('/') && !input.startsWith('?')) return `/${input}` + return input +} From 7adf56c44265c0f43b14237c7adba8b2aa23b93d Mon Sep 17 00:00:00 2001 From: Alex Cory Date: Tue, 21 Apr 2020 18:49:44 -0700 Subject: [PATCH 15/18] cache cleanup --- src/useFetch.ts | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/src/useFetch.ts b/src/useFetch.ts index 0e2c49fb..ee3b9340 100644 --- a/src/useFetch.ts +++ b/src/useFetch.ts @@ -88,22 +88,6 @@ function useFetch(...args: UseFetchArgs): UseFetch { error.current = undefined - if (response.isCached && cachePolicy === CACHE_FIRST) { - try { - res.current = response.cached as Res - const theData = await tryGetData(response.cached, defaults.data, responseType) - res.current.data = theData - res.current = interceptors.response ? await interceptors.response({ response: res.current }) : res.current - invariant('data' in res.current, 'You must have `data` field on the Response returned from your `interceptors.response`') - data.current = res.current.data as TData - if (!suspense && mounted.current) forceUpdate() - return data.current - } catch (err) { - error.current = err - if (mounted.current) forceUpdate() - } - } - // don't perform the request if there is no more data to fetch (pagination) if (perPage > 0 && !hasMore.current && !error.current) return data.current @@ -119,7 +103,11 @@ function useFetch(...args: UseFetchArgs): UseFetch { let newRes try { - newRes = await fetch(url, options) + if (response.isCached && cachePolicy === CACHE_FIRST) { + newRes = response.cached as Response + } else { + newRes = await fetch(url, options) + } res.current = newRes.clone() newData = await tryGetData(newRes, defaults.data, responseType) From 781e1f3fac882ccb791aa1059bd3e25cd5b3d250 Mon Sep 17 00:00:00 2001 From: Alex Cory Date: Wed, 22 Apr 2020 18:53:59 -0700 Subject: [PATCH 16/18] updating docs --- README.md | 4 +++- docs/README.md | 1 - 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 51982cf6..85262c43 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,6 @@ Usage ### Examples + Videos - useFetch - managed state, request, response, etc. [![](https://img.shields.io/badge/example-blue.svg)](https://codesandbox.io/s/usefetch-request-response-managed-state-ruyi3?file=/src/index.js) [![](https://img.shields.io/badge/video-red.svg)](https://www.youtube.com/watch?v=_-GujYZFCKI&list=PLZIwrWkE9rCdUybd8t3tY-mUMvXkCdenW&index=6) -- useFetch - route, path, Provider, etc. [![](https://img.shields.io/badge/example-blue.svg)](https://codesandbox.io/s/usefetch-with-provider-c78w2) [![](https://img.shields.io/badge/video-red.svg)](https://www.youtube.com/watch?v=JWDL_AVOYT0&list=PLZIwrWkE9rCdUybd8t3tY-mUMvXkCdenW&index=10) - useFetch - request/response interceptors [![](https://img.shields.io/badge/example-blue.svg)](https://codesandbox.io/s/usefetch-provider-requestresponse-interceptors-s1lex) [![](https://img.shields.io/badge/video-red.svg)](https://www.youtube.com/watch?v=3HauoWh0Jts&list=PLZIwrWkE9rCdUybd8t3tY-mUMvXkCdenW&index=8) - useFetch - retries, retryOn, retryDelay [![](https://img.shields.io/badge/example-blue.svg)](https://codesandbox.io/s/usefetch-retryon-retrydelay-s74q9) [![](https://img.shields.io/badge/video-red.svg)](https://www.youtube.com/watch?v=grE3AX-Q9ss&list=PLZIwrWkE9rCdUybd8t3tY-mUMvXkCdenW&index=9) - useFetch - abort, timeout, onAbort, onTimeout [![](https://img.shields.io/badge/video-red.svg)](https://www.youtube.com/watch?v=7SuD3ZOfu7E&list=PLZIwrWkE9rCdUybd8t3tY-mUMvXkCdenW&index=4) @@ -988,6 +987,9 @@ If you have feature requests, [submit an issue][1] to let us know what you would Todos ------ +- [ ] prefetching +- [ ] global cache state management +- [ ] optimistic updates - [ ] `persist` support for React Native - [ ] better loading state management. When using only 1 useFetch in a component and we use `Promise.all([get('/todos/1'), get('/todos/2')])` then don't have a loading true, diff --git a/docs/README.md b/docs/README.md index 9c3ae690..40e58c35 100644 --- a/docs/README.md +++ b/docs/README.md @@ -67,7 +67,6 @@ Examples + Videos ========= - useFetch - managed state, request, response, etc. [![](https://img.shields.io/badge/example-blue.svg)](https://codesandbox.io/s/usefetch-request-response-managed-state-ruyi3?file=/src/index.js) [![](https://img.shields.io/badge/video-red.svg)](https://www.youtube.com/watch?v=_-GujYZFCKI&list=PLZIwrWkE9rCdUybd8t3tY-mUMvXkCdenW&index=6) -- useFetch - route, path, Provider, etc. [![](https://img.shields.io/badge/example-blue.svg)](https://codesandbox.io/s/usefetch-with-provider-c78w2) [![](https://img.shields.io/badge/video-red.svg)](https://www.youtube.com/watch?v=JWDL_AVOYT0&list=PLZIwrWkE9rCdUybd8t3tY-mUMvXkCdenW&index=10) - useFetch - request/response interceptors [![](https://img.shields.io/badge/example-blue.svg)](https://codesandbox.io/s/usefetch-provider-requestresponse-interceptors-s1lex) [![](https://img.shields.io/badge/video-red.svg)](https://www.youtube.com/watch?v=3HauoWh0Jts&list=PLZIwrWkE9rCdUybd8t3tY-mUMvXkCdenW&index=8) - useFetch - retries, retryOn, retryDelay [![](https://img.shields.io/badge/example-blue.svg)](https://codesandbox.io/s/usefetch-retryon-retrydelay-s74q9) [![](https://img.shields.io/badge/video-red.svg)](https://www.youtube.com/watch?v=grE3AX-Q9ss&list=PLZIwrWkE9rCdUybd8t3tY-mUMvXkCdenW&index=9) - useFetch - abort, timeout, onAbort, onTimeout [![](https://img.shields.io/badge/video-red.svg)](https://www.youtube.com/watch?v=7SuD3ZOfu7E&list=PLZIwrWkE9rCdUybd8t3tY-mUMvXkCdenW&index=4) From 506bf940de23a08fd90df23a45c824ea37efd949 Mon Sep 17 00:00:00 2001 From: Alex Cory Date: Wed, 22 Apr 2020 19:03:10 -0700 Subject: [PATCH 17/18] removing conditional auto-managed state from this PR --- README.md | 40 --------------------------------- docs/README.md | 39 -------------------------------- src/__tests__/useFetch.test.tsx | 30 ------------------------- src/defaults.ts | 1 - src/useFetch.ts | 3 +-- src/useFetchArgs.ts | 4 ++-- 6 files changed, 3 insertions(+), 114 deletions(-) diff --git a/README.md b/README.md index 85262c43..c1e60daf 100644 --- a/README.md +++ b/README.md @@ -176,42 +176,6 @@ function Todos() {
- -
Conditional Auto-Managed State with Provider - -For conditional fetching via auto-managed state, if you don't want `useFetch` to execute, one way is to set `skip: true`. This would execute whenever the `id` changes and whenever the `id` exists. - -```js -import useFetch, { Provider } from 'use-http' - -function Todo({ id }) { - const defaultTodo = { title: 'default title' } - const { loading, error, data: todo } = useFetch(`/todos/${id}`, { - data: defaultTodo, - skip: !id - }, [id]) - return ( - <> - {error && 'Error!'} - {loading && 'Loading...'} - {todo.title} - - ) -} - -const App = () => ( - - - -) -``` - - - - - -
-
Suspense Mode(experimental) Auto-Managed State Can put `suspense` in 2 places. Either `useFetch` (A) or `Provider` (B). @@ -849,7 +813,6 @@ This is exactly what you would pass to the normal js `fetch`, with a little extr | `retries` | When a request fails or times out, retry the request this many times. By default it will not retry. | `0` | | `retryDelay` | You can retry with certain intervals i.e. 30 seconds `30000` or with custom logic (i.e. to increase retry intervals). | `1000` | | `retryOn` | You can retry on certain http status codes or have custom logic to decide whether to retry or not via a function. Make sure `retries > 0` otherwise it won't retry. | `[]` | -| `skip` | Tells `useFetch` not to execute if set to `true` | `false` | | `suspense` | Enables Experimental React Suspense mode. [example](https://codesandbox.io/s/usefetch-suspense-i22wv) | `false` | | `timeout` | The request will be aborted/cancelled after this amount of time. This is also the interval at which `retries` will be made at. **in milliseconds**. If set to `0`, it will not timeout except for browser defaults. | `0` | @@ -938,9 +901,6 @@ const options = { } }, - // tells useFetch not to execute if set to true - skip: false, - // enables experimental React Suspense mode suspense: true, // defaults to `false` diff --git a/docs/README.md b/docs/README.md index 40e58c35..f9902464 100644 --- a/docs/README.md +++ b/docs/README.md @@ -188,41 +188,6 @@ function Todos() { -Conditional Auto-Managed State With Provider ---------------------------------------------- - -For conditional fetching via auto-managed state, if you don't want `useFetch` to execute, one way is to set `skip: true`. This would execute whenever the `id` changes and whenever the `id` exists. - -```js -import useFetch, { Provider } from 'use-http' - -function Todo({ id }) { - const defaultTodo = { title: 'default title' } - const { loading, error, data: todo } = useFetch(`/todos/${id}`, { - data: defaultTodo, - skip: !id - }, [id]) - return ( - <> - {error && 'Error!'} - {loading && 'Loading...'} - {todo.title} - - ) -} - -const App = () => ( - - - -) -``` - - - - - - Suspense Mode Auto-Managed State ---------------------------------- @@ -800,7 +765,6 @@ This is exactly what you would pass to the normal js `fetch`, with a little extr | `retries` | When a request fails or times out, retry the request this many times. By default it will not retry. | `0` | | `retryDelay` | You can retry with certain intervals i.e. 30 seconds `30000` or with custom logic (i.e. to increase retry intervals). | `1000` | | `retryOn` | You can retry on certain http status codes or have custom logic to decide whether to retry or not via a function. Make sure `retries > 0` otherwise it won't retry. | `[]` | -| `skip` | Tells `useFetch` not to execute if set to `true` | `false` | | `suspense` | Enables Experimental React Suspense mode. [example](https://codesandbox.io/s/usefetch-suspense-i22wv) | `false` | | `timeout` | The request will be aborted/cancelled after this amount of time. This is also the interval at which `retries` will be made at. **in milliseconds**. If set to `0`, it will not timeout except for browser defaults. | `0` | @@ -890,9 +854,6 @@ const options = { } }, - // tells useFetch not to execute if set to true - skip: false, - // enables experimental React Suspense mode suspense: true, // defaults to `false` diff --git a/src/__tests__/useFetch.test.tsx b/src/__tests__/useFetch.test.tsx index 4eb10ed6..daba0154 100644 --- a/src/__tests__/useFetch.test.tsx +++ b/src/__tests__/useFetch.test.tsx @@ -148,36 +148,6 @@ describe('useFetch - handling host/path/route parsing properly', (): void => { }) }) -describe('useFetch - auto-managed state', (): void => { - afterEach((): void => { - cleanup() - fetch.resetMocks() - }) - - it('should not `skip` fetch execution when false', async (): Promise => { - fetch.resetMocks() - fetch.mockResponseOnce('Alex Cory') - const { result, waitForNextUpdate } = renderHook( - () => useFetch('/path', { skip: false }, []), // onMount === true - ) - expect(result.current.data).toEqual(undefined) - expect(result.current.loading).toBe(true) - await waitForNextUpdate() - expect(result.current.data).toEqual('Alex Cory') - expect(result.current.loading).toBe(false) - expect(fetch.mock.calls.length).toBe(1) - }) - - it('should `skip` fetch execution when true', async (): Promise => { - const { result } = renderHook( - () => useFetch('/path', { skip: true }, []), // onMount === true - ) - expect(result.current.data).toEqual(undefined) - expect(result.current.loading).toBe(false) - expect(fetch.mock.calls.length).toBe(0) - }) -}) - describe('useFetch - responseType', (): void => { afterEach((): void => { cleanup() diff --git a/src/defaults.ts b/src/defaults.ts index ca7bd128..634d377b 100644 --- a/src/defaults.ts +++ b/src/defaults.ts @@ -19,7 +19,6 @@ export const useFetchArgsDefaults: UseFetchArgsReturn = { retries: 0, retryDelay: 1000, retryOn: [], - skip: false, suspense: false, timeout: 0, // defaults diff --git a/src/useFetch.ts b/src/useFetch.ts index ee3b9340..10157d45 100644 --- a/src/useFetch.ts +++ b/src/useFetch.ts @@ -41,7 +41,6 @@ function useFetch(...args: UseFetchArgs): UseFetch { retries, retryDelay, retryOn, - skip, suspense, timeout, ...defaults @@ -68,7 +67,7 @@ function useFetch(...args: UseFetchArgs): UseFetch { const makeFetch = useDeepCallback((method: HTTPMethod): FetchData => { const doFetch = async (routeOrBody?: RouteOrBody, body?: UFBody): Promise => { - if (isServer || skip) return // for now, we don't do anything on the server + if (isServer) return // for now, we don't do anything on the server controller.current = new AbortController() controller.current.signal.onabort = onAbort const theController = controller.current diff --git a/src/useFetchArgs.ts b/src/useFetchArgs.ts index 94bd820a..23c9fa70 100644 --- a/src/useFetchArgs.ts +++ b/src/useFetchArgs.ts @@ -63,13 +63,13 @@ export default function useFetchArgs( return defaults.dependencies }, [optionsOrOverwriteGlobalOrDeps, deps]) - const { cacheLife, retries, retryDelay, retryOn, skip } = options + const { cacheLife, retries, retryDelay, retryOn } = options invariant(Number.isInteger(cacheLife) && cacheLife >= 0, '`cacheLife` must be a number >= 0') invariant(Number.isInteger(retries) && retries >= 0, '`retries` must be a number >= 0') invariant(isFunction(retryDelay) || Number.isInteger(retryDelay as number) && retryDelay >= 0, '`retryDelay` must be a positive number or a function returning a positive number.') const isValidRetryOn = isFunction(retryOn) || (Array.isArray(retryOn) && retryOn.every(isPositiveNumber)) invariant(isValidRetryOn, '`retryOn` must be an array of positive numbers or a function returning a boolean.') - const loading = (options.loading || Array.isArray(dependencies)) && !skip + const loading = options.loading || Array.isArray(dependencies) const interceptors = useMemo((): Interceptors => { const final: Interceptors = {} From 6ce9913d9fe432e68e00391be5efcf37108fcb5a Mon Sep 17 00:00:00 2001 From: Alex Cory Date: Wed, 22 Apr 2020 19:03:58 -0700 Subject: [PATCH 18/18] forgot to remove skip from types.ts --- src/types.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/types.ts b/src/types.ts index c0536dfa..12248662 100644 --- a/src/types.ts +++ b/src/types.ts @@ -189,7 +189,6 @@ export interface CustomOptions { retries: number retryOn: RetryOn retryDelay: RetryDelay - skip: boolean suspense: boolean timeout: number } @@ -229,7 +228,6 @@ export type UseFetchArgsReturn = { retries: number retryDelay: RetryDelay retryOn: RetryOn | undefined - skip: boolean suspense: boolean timeout: number // defaults