From fbc2711b1379b18906075722ac4ace368dbfaeaf Mon Sep 17 00:00:00 2001 From: Joshua James Allwood Date: Mon, 22 Jan 2024 10:30:50 +0000 Subject: [PATCH 1/4] refactor(makeServerFetchye): added run to existing method --- packages/fetchye/src/computeKey.js | 14 ++++++++++++-- packages/fetchye/src/makeServerFetchye.js | 17 +++++++++++++---- packages/fetchye/src/runAsync.js | 3 ++- packages/fetchye/src/useFetchye.js | 17 +++++------------ 4 files changed, 32 insertions(+), 19 deletions(-) diff --git a/packages/fetchye/src/computeKey.js b/packages/fetchye/src/computeKey.js index ee20d89..2ce5904 100644 --- a/packages/fetchye/src/computeKey.js +++ b/packages/fetchye/src/computeKey.js @@ -16,9 +16,19 @@ import computeHash from 'object-hash'; import mapHeaderNamesToLowerCase from './mapHeaderNamesToLowerCase'; +import { defaultMapOptionsToKey } from './defaultMapOptionsToKey'; +import { handleDynamicHeaders } from './handleDynamicHeaders'; + +export const computeKey = (key, { + mapOptionsToKey = (options) => options, + ...options +} = {}) => { + const { + headers, + mapKeyToCacheKey, + ...restOfOptions + } = defaultMapOptionsToKey(mapOptionsToKey(handleDynamicHeaders(options))); -export const computeKey = (key, options) => { - const { headers, mapKeyToCacheKey, ...restOfOptions } = options; const nextOptions = { ...restOfOptions }; if (headers) { nextOptions.headers = mapHeaderNamesToLowerCase(headers); diff --git a/packages/fetchye/src/makeServerFetchye.js b/packages/fetchye/src/makeServerFetchye.js index 1363061..25053b1 100644 --- a/packages/fetchye/src/makeServerFetchye.js +++ b/packages/fetchye/src/makeServerFetchye.js @@ -18,7 +18,6 @@ import { ssrFetcher } from 'fetchye-core'; import SimpleCache from './SimpleCache'; import { runAsync } from './runAsync'; import { computeKey } from './computeKey'; -import { defaultMapOptionsToKey } from './defaultMapOptionsToKey'; import { coerceSsrField } from './queryHelpers'; const makeServerFetchye = ({ @@ -27,11 +26,19 @@ const makeServerFetchye = ({ fetchClient, }) => async ( key, - { mapOptionsToKey = (options) => options, ...options } = { }, + options = {}, fetcher = ssrFetcher ) => { const { cacheSelector } = cache; - const computedKey = computeKey(key, defaultMapOptionsToKey(mapOptionsToKey(options))); + const computedKey = computeKey(key, options); + const run = () => runAsync({ + dispatch, + computedKey, + fetcher, + fetchClient, + options, + }); + if (!getState || !dispatch || !cacheSelector) { const res = await runAsync({ dispatch: () => {}, computedKey, fetcher, fetchClient, options, @@ -39,6 +46,7 @@ const makeServerFetchye = ({ return { data: coerceSsrField(res.data), error: coerceSsrField(res.error), + run, }; } const state = cacheSelector(getState()); @@ -50,9 +58,10 @@ const makeServerFetchye = ({ return { data: coerceSsrField(res.data), error: coerceSsrField(res.error), + run, }; } - return { data: coerceSsrField(data), error: coerceSsrField(error) }; + return { data: coerceSsrField(data), error: coerceSsrField(error), run }; }; export default makeServerFetchye; diff --git a/packages/fetchye/src/runAsync.js b/packages/fetchye/src/runAsync.js index 9f7b661..8ed9a77 100644 --- a/packages/fetchye/src/runAsync.js +++ b/packages/fetchye/src/runAsync.js @@ -20,6 +20,7 @@ import { errorAction, } from 'fetchye-core'; +import { handleDynamicHeaders } from './handleDynamicHeaders'; export const runAsync = async ({ dispatch, computedKey, fetcher, fetchClient, options, @@ -28,7 +29,7 @@ export const runAsync = async ({ const { payload: data, error: requestError, - } = await fetcher(fetchClient, computedKey.key, options); + } = await fetcher(fetchClient, computedKey.key, handleDynamicHeaders(options)); if (!requestError) { dispatch(setAction({ hash: computedKey.hash, value: data })); } else { diff --git a/packages/fetchye/src/useFetchye.js b/packages/fetchye/src/useFetchye.js index 5b79c76..8280055 100644 --- a/packages/fetchye/src/useFetchye.js +++ b/packages/fetchye/src/useFetchye.js @@ -21,8 +21,6 @@ import { isLoading, } from './queryHelpers'; import { useFetchyeContext } from './useFetchyeContext'; -import { defaultMapOptionsToKey } from './defaultMapOptionsToKey'; -import { handleDynamicHeaders } from './handleDynamicHeaders'; const passInitialData = (value, initialValue, numOfRenders) => (numOfRenders === 1 ? value || initialValue @@ -30,15 +28,14 @@ const passInitialData = (value, initialValue, numOfRenders) => (numOfRenders === const useFetchye = ( key, - { mapOptionsToKey = (options) => options, ...options } = { }, + options = {}, fetcher = undefined ) => { const { defaultFetcher, useFetchyeSelector, dispatch, fetchClient, } = useFetchyeContext(); - const dynamicOptions = handleDynamicHeaders(options); const selectedFetcher = typeof fetcher === 'function' ? fetcher : defaultFetcher; - const computedKey = computeKey(key, defaultMapOptionsToKey(mapOptionsToKey(dynamicOptions))); + const computedKey = computeKey(key, options); const selectorState = useFetchyeSelector(computedKey.hash); // create a render version manager using refs const numOfRenders = useRef(0); @@ -55,7 +52,7 @@ const useFetchye = ( const { loading, data, error } = selectorState.current; if (!loading && !data && !error) { runAsync({ - dispatch, computedKey, fetcher: selectedFetcher, fetchClient, options: dynamicOptions, + dispatch, computedKey, fetcher: selectedFetcher, fetchClient, options, }); } }); @@ -78,16 +75,12 @@ const useFetchye = ( numOfRenders.current ), run() { - const runOptions = handleDynamicHeaders(options); - const runComputedKey = typeof options.headers === 'function' - ? computeKey(key, defaultMapOptionsToKey(mapOptionsToKey(runOptions))) - : computedKey; return runAsync({ dispatch, - computedKey: runComputedKey, + computedKey, fetcher: selectedFetcher, fetchClient, - options: runOptions, + options, }); }, }; From 114522828a9f600f72ff86659b5fd3dfe7456ba2 Mon Sep 17 00:00:00 2001 From: Joshua James Allwood Date: Mon, 22 Jan 2024 16:18:26 +0000 Subject: [PATCH 2/4] test(fetchye): coverage for makeServerFetchye changes --- packages/fetchye/__tests__/computeKey.spec.js | 23 ++++ .../__tests__/makeServerFetchye.spec.js | 122 +++++++----------- .../fetchye/__tests__/useFetchye.spec.jsx | 28 ---- 3 files changed, 69 insertions(+), 104 deletions(-) diff --git a/packages/fetchye/__tests__/computeKey.spec.js b/packages/fetchye/__tests__/computeKey.spec.js index c7b934f..e4e8d50 100644 --- a/packages/fetchye/__tests__/computeKey.spec.js +++ b/packages/fetchye/__tests__/computeKey.spec.js @@ -42,6 +42,14 @@ describe('computeKey', () => { } `); }); + it('should return an object if no options are passed', () => { + expect(computeKey(() => 'abcd')).toMatchInlineSnapshot(` + Object { + "hash": "037ace2918f4083eda9c4be34cccb93de5051b5a", + "key": "abcd", + } + `); + }); it('should return false if key func throws error', () => { expect( computeKey(() => { @@ -89,6 +97,21 @@ describe('computeKey', () => { expect(mappedHash).toBe(unmappedHash); }); + it('should call dynamic headers function and return a different hash when dynamic headers change', () => { + const key = 'abcd'; + let headerCount = 0; + const options = { + headers: () => { + headerCount += 1; + return { dynamicHeader: headerCount }; + }, + }; + const { hash: mappedHash1 } = computeKey(key, options); + const { hash: mappedHash2 } = computeKey(key, options); + expect(mappedHash1).not.toBe(mappedHash2); + expect(headerCount).toBe(2); + }); + it('should pass generated cacheKey to the underlying hash function along with the options, and return the un-mapped key to the caller', () => { const computedKey = computeKey(() => 'abcd', { mapKeyToCacheKey: (key, options) => `${key.toUpperCase()}-${options.optionKeyMock}`, diff --git a/packages/fetchye/__tests__/makeServerFetchye.spec.js b/packages/fetchye/__tests__/makeServerFetchye.spec.js index ae15c1d..5c352f0 100644 --- a/packages/fetchye/__tests__/makeServerFetchye.spec.js +++ b/packages/fetchye/__tests__/makeServerFetchye.spec.js @@ -18,9 +18,6 @@ import { createStore } from 'redux'; import makeServerFetchye from '../src/makeServerFetchye'; import SimpleCache from '../src/SimpleCache'; -const cache = SimpleCache(); -const store = createStore(cache.reducer, cache.reducer(undefined, { type: '' })); - global.console.error = jest.fn(); const defaultPayload = { @@ -34,87 +31,65 @@ const defaultPayload = { }), }; +const expectedMakeServerFetchyeResponseSnapshot = ` + Object { + "data": Object { + "body": Object { + "fakeData": true, + }, + "headers": Object { + "content-type": "application/json", + }, + "ok": true, + "status": 200, + }, + "error": null, + "run": [Function], + } +`; + describe('makeServerFetchye', () => { + let cache; + let store; + let fetchClient; + beforeEach(() => { + cache = SimpleCache(); + store = createStore(cache.reducer, cache.reducer(undefined, { type: '' })); + fetchClient = jest.fn(async () => ({ + ...defaultPayload, + })); + }); + + afterEach(() => { jest.resetAllMocks(); }); it('should return data in success state', async () => { - const fetchClient = jest.fn(async () => ({ - ...defaultPayload, - })); const fetchyeRes = await makeServerFetchye({ store, cache, fetchClient, })('http://example.com'); - expect(fetchyeRes).toMatchInlineSnapshot(` - Object { - "data": Object { - "body": Object { - "fakeData": true, - }, - "headers": Object { - "content-type": "application/json", - }, - "ok": true, - "status": 200, - }, - "error": null, - } - `); + expect(fetchyeRes).toMatchInlineSnapshot(expectedMakeServerFetchyeResponseSnapshot); }); it('should return data in success state when using default cache', async () => { - const fetchClient = jest.fn(async () => ({ - ...defaultPayload, - })); const fetchyeRes = await makeServerFetchye({ store, fetchClient, })('http://example.com'); - expect(fetchyeRes).toMatchInlineSnapshot(` - Object { - "data": Object { - "body": Object { - "fakeData": true, - }, - "headers": Object { - "content-type": "application/json", - }, - "ok": true, - "status": 200, - }, - "error": null, - } - `); + expect(fetchyeRes).toMatchInlineSnapshot(expectedMakeServerFetchyeResponseSnapshot); }); it('should return data in success state if no cache and no store provided', async () => { - const fetchClient = jest.fn(async () => ({ - ...defaultPayload, - })); const fetchyeRes = await makeServerFetchye({ fetchClient, })('http://example.com'); - expect(fetchyeRes).toMatchInlineSnapshot(` - Object { - "data": Object { - "body": Object { - "fakeData": true, - }, - "headers": Object { - "content-type": "application/json", - }, - "ok": true, - "status": 200, - }, - "error": null, - } - `); + expect(fetchyeRes).toMatchInlineSnapshot(expectedMakeServerFetchyeResponseSnapshot); }); it('should return null in the error state', async () => { - const fetchClient = jest.fn(async () => { + fetchClient = jest.fn(async () => { throw new Error('fake error'); }); const fetchyeRes = await makeServerFetchye({ @@ -134,13 +109,11 @@ describe('makeServerFetchye', () => { Object { "data": null, "error": null, + "run": [Function], } `); }); it('should return previously loaded data', async () => { - const fetchClient = jest.fn(async () => ({ - ...defaultPayload, - })); const fetchye = makeServerFetchye({ store, cache, @@ -150,20 +123,17 @@ describe('makeServerFetchye', () => { const fetchyeResTwo = await fetchye('http://example.com/two'); expect(fetchClient).toHaveBeenCalledTimes(1); - expect(fetchyeResTwo).toMatchInlineSnapshot(` - Object { - "data": Object { - "body": Object { - "fakeData": true, - }, - "headers": Object { - "content-type": "application/json", - }, - "ok": true, - "status": 200, - }, - "error": null, - } - `); + expect(fetchyeResTwo).toMatchInlineSnapshot(expectedMakeServerFetchyeResponseSnapshot); + }); + it('should reload the data is the run function returned is called', async () => { + const fetchye = makeServerFetchye({ + store, + cache, + fetchClient, + }); + const fetchyeResTwo = await fetchye('http://example.com/two'); + await fetchyeResTwo.run(); + expect(fetchClient).toHaveBeenCalledTimes(2); + expect(fetchyeResTwo).toMatchInlineSnapshot(expectedMakeServerFetchyeResponseSnapshot); }); }); diff --git a/packages/fetchye/__tests__/useFetchye.spec.jsx b/packages/fetchye/__tests__/useFetchye.spec.jsx index b60893a..ab34fbc 100644 --- a/packages/fetchye/__tests__/useFetchye.spec.jsx +++ b/packages/fetchye/__tests__/useFetchye.spec.jsx @@ -278,34 +278,6 @@ describe('useFetchye', () => { ] `); }); - it('should return data when run method is called with dynamic headers', async () => { - let fetchyeRes; - global.fetch = jest.fn(async () => ({ - ...defaultPayload, - })); - render( - - {React.createElement(() => { - fetchyeRes = useFetchye('http://example.com/one', { defer: true, headers: () => ({ dynamicHeader: 'dynamic value' }) }); - return null; - })} - - ); - await fetchyeRes.run(); - expect(global.fetch.mock.calls).toMatchInlineSnapshot(` - Array [ - Array [ - "http://example.com/one", - Object { - "defer": true, - "headers": Object { - "dynamicHeader": "dynamic value", - }, - }, - ], - ] - `); - }); it('should use fetcher in hook over provider fetcher', async () => { const customFetchClient = jest.fn(async () => ({ ...defaultPayload, From 811a415d7e939dc7444e095b2fdee4b30fbbadac Mon Sep 17 00:00:00 2001 From: Joshua James Allwood Date: Tue, 13 Feb 2024 15:29:43 +0000 Subject: [PATCH 3/4] fix(useFetchye): need to recalculate key on run, maybe dynamic --- README.md | 309 +++++++++--------- .../fetchye/__tests__/useFetchye.spec.jsx | 34 +- packages/fetchye/src/makeServerFetchye.js | 2 +- packages/fetchye/src/useFetchye.js | 2 +- 4 files changed, 171 insertions(+), 176 deletions(-) diff --git a/README.md b/README.md index 5f92e5a..28cacc8 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,7 @@ const MyComponent = () => { ## โฌ‡๏ธ Install & Setup **Contents** + * [Quick Install](#quick-install) * [FetchyeProvider Install](#fetchyeprovider-install) * [FetchyeReduxProvider Install](#fetchyereduxprovider-install) @@ -43,12 +44,14 @@ const MyComponent = () => { ### Quick Install ->๐Ÿ’ก Makes use of Headless per-Hook Cache Mode +> ๐Ÿ’ก Makes use of Headless per-Hook Cache Mode **Pros** + - Painless and Quick **Cons** + - No shared caching - No de-duplication of API calls @@ -76,14 +79,16 @@ const MyComponent = () => { ### `FetchyeProvider` Install ->๐Ÿ’ก When you want a central cache but no extra dependencies +> ๐Ÿ’ก When you want a central cache but no extra dependencies **Pros** + - Easy - Shared Cache - De-duplication of API calls **Cons** + - No Redux Dev Tools for debugging and cache inspection - Limited centralized server-side data hydration support @@ -126,9 +131,10 @@ const MyComponent = () => { ### `FetchyeReduxProvider` Install ->๐Ÿ’ก When you want a central cache integrated with a Redux based project +> ๐Ÿ’ก When you want a central cache integrated with a Redux based project **Pros** + - Easy if you know Redux - Shared Cache - De-duplication of API calls @@ -136,6 +142,7 @@ const MyComponent = () => { - Excellent centralized server-side data hydration support **Cons** + - More steps and dependencies Add `fetchye` and its needed optional dependencies: @@ -189,9 +196,10 @@ const MyComponent = () => { ### One App Install ->๐Ÿ’ก For when you use the [One App](https://github.com/americanexpress/one-app) Micro-Frontend Framework +> ๐Ÿ’ก For when you use the [One App](https://github.com/americanexpress/one-app) Micro-Frontend Framework **Pros** + - Shared Cache - De-duplication of API calls - Redux Dev Tools for debugging and cache inspection @@ -201,19 +209,16 @@ const MyComponent = () => { - Minimal configuration **Cons** + - More steps and dependencies ``` npm i -S fetchye fetchye-one-app ``` -`fetchye-one-app` provides pre-configured `provider`, `cache`, `oneFetchye` -and `oneCacheSelector` to ensure that all modules use the same cache and reduce the chance for cache misses. -These all have restricted APIs to reduce the chance for misconfiguration however if you require more control/customization -use [`ImmutableCache`](#immutablecache), [`FetchyeReduxProvider`](#fetchyereduxprovider) and [`makeServerFetchye`](#makeserverfetchye). Please bear in mind that this can impact modules which are do not use the same configuration. +`fetchye-one-app` provides pre-configured `provider`, `cache`, `oneFetchye` and `oneCacheSelector` to ensure that all modules use the same cache and reduce the chance for cache misses. These all have restricted APIs to reduce the chance for misconfiguration however if you require more control/customization use [`ImmutableCache`](#immutablecache), [`FetchyeReduxProvider`](#fetchyereduxprovider) and [`makeServerFetchye`](#makeserverfetchye). Please bear in mind that this can impact modules which are do not use the same configuration. -Add the `` component from `fetchye-one-app` to your Root Holocron Module, -and add the reducer from `OneCache` scoped under `fetchye`: +Add the `` component from `fetchye-one-app` to your Root Holocron Module, and add the reducer from `OneCache` scoped under `fetchye`: ```jsx // ... @@ -222,7 +227,7 @@ import { OneFetchyeProvider, OneCache } from 'fetchye-one-app'; const MyModuleRoot = ({ children }) => ( <> - { /* OneFetchyeProvider is configured to use OneCache */ } + { /* OneFetchyeProvider is configured to use OneCache */} {/* Use your Router to supply children components containing useFetchye */} {children} @@ -259,8 +264,7 @@ const MyComponent = () => { }; ``` -This minimal configuration works as the provider, cache and makeOneServerFetchye, mentioned later, -all follow expected conventions. +This minimal configuration works as the provider, cache and makeOneServerFetchye, mentioned later, all follow expected conventions. ## ๐Ÿคนโ€ Usage @@ -342,6 +346,7 @@ const NewBookForm = () => { When you neeed to abort the execution of requests inflight, passing a signal from the [Abort Controller](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) API to `useFetchye` as an option will enable this. Considering `useFetchye` is a wrapper around fetch, passing a signal is the same and provides the same functionality as demonstrated below. + ```jsx import React, { useEffect } from 'react'; import { useFetchye } from 'fetchye'; @@ -359,6 +364,7 @@ const AbortComponent = () => { ); }; ``` + Instead of setting up a `useEffect` within the component it's possible to pass a hook to signal using packages such as [use-unmount-signal](https://www.npmjs.com/package/use-unmount-signal/v/1.0.0). @@ -469,7 +475,11 @@ import React from 'react'; import { useFetchye } from 'fetchye'; const BookList = ({ genre }) => { - const { isLoading, data: booksData, run } = useFetchye(`http://example.com/api/books/?genre=${genre}`); + const { + isLoading, + data: booksData, + run + } = useFetchye(`http://example.com/api/books/?genre=${genre}`); if (isLoading) { return (

Loading...

); @@ -486,8 +496,7 @@ const BookList = ({ genre }) => { ### Custom Fetcher -Custom fetchers allow for creating reusable data fetching logic for specific -APIs or custom needs. They allow for a centrally provided +Custom fetchers allow for creating reusable data fetching logic for specific APIs or custom needs. They allow for a centrally provided `fetchClient` which wraps that client on a per `useFetchye` request basis. ```jsx @@ -582,7 +591,7 @@ const BookList = ({ ssl }) => { } return ( - {/* Render data */} + {/* Render data */ } ); }; ``` @@ -621,20 +630,18 @@ const BookList = () => { } return ( - {/* Render data */} + {/* Render data */ } ); }; export default BookList; ``` - ### SSR #### One App SSR -Using `oneFetchye` from `fetchye-one-app` ensures that the cache will -always be configured correctly. +Using `oneFetchye` from `fetchye-one-app` ensures that the cache will always be configured correctly. ```jsx import React from 'react'; @@ -649,7 +656,7 @@ const BookList = () => { } return ( - {/* Render data */} + {/* Render data */ } ); }; @@ -686,7 +693,7 @@ export default function IndexPage({ initialBookList }) { } return ( - {/* Render data */} + {/* Render data */ } ); } @@ -716,7 +723,8 @@ export async function getServerSideProps() { ## Write your own Cache -> ๐Ÿ’ฌ **Note**: This is for advanced users with special cases. Advanced users should understand Redux design pattern concepts about [Reducers](https://redux.js.org/basics/reducers) and [Actions](https://redux.js.org/basics/actions) before continuing. +> ๐Ÿ’ฌ **Note +**: This is for advanced users with special cases. Advanced users should understand Redux design pattern concepts about [Reducers](https://redux.js.org/basics/reducers) and [Actions](https://redux.js.org/basics/actions) before continuing. Sometimes, the basic opinions of the cache may not be enough for a project's use case. We can create a very basic new Cache configuration like so: @@ -821,11 +829,11 @@ const { isLoading, data, error, run } = useFetchye(key, { defer: Boolean, mapOpt **Arguments** -| name | type | required | description | -|---|---|---|---| -| `key` | `String` or `() => String` | `true` | A string or function returning a string that factors into cache key creation. *Defaults to URL compatible string*. | -| `options` | `Object` | `false` | Options to pass through to ES6 Fetch. See **Options** table for the exceptions to this rule. The `options` object factors into cache key creation. | -| `fetcher` | `async (fetchClient: Fetch, key: String, options: Options) => ({ payload: Object, error?: Object })` | `false` | The async function that calls `fetchClient` by key and options. Returns a `payload` with outcome of `fetchClient` and an optional `error` object. | +| name | type | required | description | +|-----------|------------------------------------------------------------------------------------------------------|----------|----------------------------------------------------------------------------------------------------------------------------------------------------| +| `key` | `String` or `() => String` | `true` | A string or function returning a string that factors into cache key creation. *Defaults to URL compatible string*. | +| `options` | `Object` | `false` | Options to pass through to ES6 Fetch. See **Options** table for the exceptions to this rule. The `options` object factors into cache key creation. | +| `fetcher` | `async (fetchClient: Fetch, key: String, options: Options) => ({ payload: Object, error?: Object })` | `false` | The async function that calls `fetchClient` by key and options. Returns a `payload` with outcome of `fetchClient` and an optional `error` object. | **Options** @@ -840,12 +848,12 @@ const { isLoading, data, error, run } = useFetchye(key, { defer: Boolean, mapOpt **Returns** -| name | type | description | -|---|---|---| -| `isLoading` | `Boolean` | A boolean to indicate whether in loading state or not. | -| `data` | `Object` | A result of a `fetchClient` query. *Defaults to returning `{ status, body, ok, headers }` from `fetchClient` response* | -| `error?` | `Object` | An object containing an error if present. *Defaults to an `Error` object with a thrown `fetch` error. This is not for API errors (e.g. Status 500 or 400). See `data` for that* | -| `run` | `async () => {}` | A function for bypassing the cache and firing an API call. This will cause `isLoading === true` and update the cache based on the result. | +| name | type | description | +|-------------|------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `isLoading` | `Boolean` | A boolean to indicate whether in loading state or not. | +| `data` | `Object` | A result of a `fetchClient` query. *Defaults to returning `{ status, body, ok, headers }` from `fetchClient` response* | +| `error?` | `Object` | An object containing an error if present. *Defaults to an `Error` object with a thrown `fetch` error. This is not for API errors (e.g. Status 500 or 400). See `data` for that* | +| `run` | `async () => {}` | A function for bypassing the cache and firing an API call. This will cause `isLoading === true` and update the cache based on the result. | ### `makeServerFetchye` @@ -861,26 +869,26 @@ const { data, error } = await fetchye(key, options, fetcher); **`makeServerFetchye` Arguments** -| name | type | required | description | -|---|---|---|---| -| `cache` | `Cache` | `true` | Fetchye `Cache` object. | -| `fetchClient` | `ES6Fetch` | `true` | A [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) compatible function. | +| name | type | required | description | +|---------------|------------|----------|------------------------------------------------------------------------------------------------| +| `cache` | `Cache` | `true` | Fetchye `Cache` object. | +| `fetchClient` | `ES6Fetch` | `true` | A [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) compatible function. | **`fetchye` Arguments** -| name | type | required | description | -|---|---|---|---| -| `key` | `String` or `() => String` | `true` | A string or function returning a string that factors into cache key creation. *Defaults to URL compatible string*. | -| `options` | `ES6FetchOptions` | `false` | Options to pass through to [ES6 Fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API). | -| `fetcher` | `async (fetchClient: Fetch, key: String, options: Options) => ({ payload: Object, error?: Object })` | `false` | The async function that calls `fetchClient` by key and options. Returns a `payload` with outcome of `fetchClient` and an optional `error` object. | +| name | type | required | description | +|-----------|------------------------------------------------------------------------------------------------------|----------|---------------------------------------------------------------------------------------------------------------------------------------------------| +| `key` | `String` or `() => String` | `true` | A string or function returning a string that factors into cache key creation. *Defaults to URL compatible string*. | +| `options` | `ES6FetchOptions` | `false` | Options to pass through to [ES6 Fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API). | +| `fetcher` | `async (fetchClient: Fetch, key: String, options: Options) => ({ payload: Object, error?: Object })` | `false` | The async function that calls `fetchClient` by key and options. Returns a `payload` with outcome of `fetchClient` and an optional `error` object. | **`fetchye` Returns** -| name | type | description | -|---|---|---| -| `data` | `Object` | A result of a `fetchClient` query. *Defaults to returning `{ status, body, ok, headers }` from `fetchClient` response* | -| `error?` | `Object` | An object containing an error if present. *Defaults to an `Error` object with a thrown `fetch` error. This is not for API errors (e.g. Status 500 or 400). See `data` for that* | - +| name | type | description | +|----------|------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `data` | `Object` | A result of a `fetchClient` query. *Defaults to returning `{ status, body, ok, headers }` from `fetchClient` response* | +| `error?` | `Object` | An object containing an error if present. *Defaults to an `Error` object with a thrown `fetch` error. This is not for API errors (e.g. Status 500 or 400). See `data` for that* | +| `run` | `async () => {}` | A function for bypassing the cache and firing an API call. Can we awaited. | ### `makeOneServerFetchye` @@ -898,27 +906,27 @@ const { data, error } = await fetchye(key, options, fetcher); **`makeOneServerFetchye` Arguments** -| name | type | required | description | -|---|---|---|---| -| `cache` | `Cache` | `false` | *Defaults to OneCache* Fetchye `Cache` object. | -| `fetchClient` | `ES6Fetch` | `true` | A [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) compatible function. | -| `store` | `Store` | `true` | A [Redux Store](https://redux.js.org/api/store) | +| name | type | required | description | +|---------------|------------|----------|------------------------------------------------------------------------------------------------| +| `cache` | `Cache` | `false` | *Defaults to OneCache* Fetchye `Cache` object. | +| `fetchClient` | `ES6Fetch` | `true` | A [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) compatible function. | +| `store` | `Store` | `true` | A [Redux Store](https://redux.js.org/api/store) | **`fetchye` Arguments** -| name | type | required | description | -|---|---|---|---| -| `key` | `String` or `() => String` | `true` | A string or function returning a string that factors into cache key creation. *Defaults to URL compatible string*. | -| `options` | `ES6FetchOptions` | `false` | Options to pass through to [ES6 Fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API). | -| `fetcher` | `async (fetchClient: Fetch, key: String, options: Options) => ({ payload: Object, error?: Object })` | `false` | The async function that calls `fetchClient` by key and options. Returns a `payload` with outcome of `fetchClient` and an optional `error` object. | +| name | type | required | description | +|-----------|------------------------------------------------------------------------------------------------------|----------|---------------------------------------------------------------------------------------------------------------------------------------------------| +| `key` | `String` or `() => String` | `true` | A string or function returning a string that factors into cache key creation. *Defaults to URL compatible string*. | +| `options` | `ES6FetchOptions` | `false` | Options to pass through to [ES6 Fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API). | +| `fetcher` | `async (fetchClient: Fetch, key: String, options: Options) => ({ payload: Object, error?: Object })` | `false` | The async function that calls `fetchClient` by key and options. Returns a `payload` with outcome of `fetchClient` and an optional `error` object. | **`fetchye` Returns** -| name | type | description | -|---|---|---| -| `data` | `Object` | A result of a `fetchClient` query. *Defaults to returning `{ status, body, ok, headers }` from `fetchClient` response* | -| `error?` | `Object` | An object containing an error if present. *Defaults to an `Error` object with a thrown `fetch` error. This is not for API errors (e.g. Status 500 or 400). See `data` for that* | - +| name | type | description | +|----------|------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `data` | `Object` | A result of a `fetchClient` query. *Defaults to returning `{ status, body, ok, headers }` from `fetchClient` response* | +| `error?` | `Object` | An object containing an error if present. *Defaults to an `Error` object with a thrown `fetch` error. This is not for API errors (e.g. Status 500 or 400). See `data` for that* | +| `run` | `async () => {}` | A function for bypassing the cache and firing an API call. Can we awaited. | ### oneFetchye @@ -927,26 +935,26 @@ Call fetchye in an imperative context, such as in One App's loadModuleData, in a **Shape** ``` -const { data, error } = await dispatch(onefetchye(key, options, fetcher)); +const { data, error } = await dispatch(oneFetchye(key, options, fetcher)); ``` -**`onefetchye` Arguments** +**`oneFetchye` Arguments** -| name | type | required | description | -|-----------|------------------------------------------------------------------------------------------------------|------------|---------------------------------------------------------------------------------------------------------------------------------------------------| -| `key` | `String` or `() => String` | `true` | A string or function returning a string that factors into cache key creation. *Defaults to URL compatible string*. | -| `options` | `ES6FetchOptions` | `false` | Options to pass through to [ES6 Fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API). | -| `fetcher` | `async (fetchClient: Fetch, key: String, options: Options) => ({ payload: Object, error?: Object })` | `false` | The async function that calls `fetchClient` by key and options. Returns a `payload` with outcome of `fetchClient` and an optional `error` object. | +| name | type | required | description | +|-----------|------------------------------------------------------------------------------------------------------|----------|---------------------------------------------------------------------------------------------------------------------------------------------------| +| `key` | `String` or `() => String` | `true` | A string or function returning a string that factors into cache key creation. *Defaults to URL compatible string*. | +| `options` | `ES6FetchOptions` | `false` | Options to pass through to [ES6 Fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API). | +| `fetcher` | `async (fetchClient: Fetch, key: String, options: Options) => ({ payload: Object, error?: Object })` | `false` | The async function that calls `fetchClient` by key and options. Returns a `payload` with outcome of `fetchClient` and an optional `error` object. | **`onefetchye` Returns** A promise resolving to an object with the below keys: -| name | type | description | -|----------|----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `data` | `Object` | A result of a `fetchClient` query. *Defaults to returning `{ status, body, ok, headers }` from `fetchClient` response* | -| `error?` | `Object` | An object containing an error if present. *Defaults to an `Error` object with a thrown `fetch` error. This is not for API errors (e.g. Status 500 or 400). See `data` for that* | - +| name | type | description | +|----------|------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `data` | `Object` | A result of a `fetchClient` query. *Defaults to returning `{ status, body, ok, headers }` from `fetchClient` response* | +| `error?` | `Object` | An object containing an error if present. *Defaults to an `Error` object with a thrown `fetch` error. This is not for API errors (e.g. Status 500 or 400). See `data` for that* | +| `run` | `async () => {}` | A function for bypassing the cache and firing an API call. Can we awaited. | ### Providers @@ -966,11 +974,11 @@ A React Context Provider that holds the centralized cache for all the `useFetchy **Props** -| name | type | required | description | -|---|---|---|---| -| `fetchClient` | `ES6Fetch` | `true` | A [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) compatible function. | -| `cache` | `Cache` | `false` | Fetchye `Cache` object. *Defaults to `SimpleCache`* | -| `initialData` | `Object` | `false` | Initial state to feed into Cache Configuration `reducer` | +| name | type | required | description | +|---------------|------------|----------|------------------------------------------------------------------------------------------------| +| `fetchClient` | `ES6Fetch` | `true` | A [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) compatible function. | +| `cache` | `Cache` | `false` | Fetchye `Cache` object. *Defaults to `SimpleCache`* | +| `initialData` | `Object` | `false` | Initial state to feed into Cache Configuration `reducer` | #### `FetchyeReduxProvider` @@ -994,16 +1002,16 @@ import { FetchyeReduxProvider } from "fetchye-redux-provider"; **Context** -| name | type | required | description | -|---|---|---|---| -| `ReactReduxContext` | `ReactReduxContext` | `true` | A [Redux Context](https://react-redux.js.org/api/provider) from a ``. | +| name | type | required | description | +|---------------------|---------------------|----------|-----------------------------------------------------------------------------------| +| `ReactReduxContext` | `ReactReduxContext` | `true` | A [Redux Context](https://react-redux.js.org/api/provider) from a ``. | **Props** -| name | type | required | description | -|---|---|---|---| -| `fetchClient` | `ES6Fetch` | `true` | A [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) compatible function. | -| `cache` | `Cache` | `false` | Fetchye `Cache` object. *Defaults to `SimpleCache`* | +| name | type | required | description | +|---------------|------------|----------|------------------------------------------------------------------------------------------------| +| `fetchClient` | `ES6Fetch` | `true` | A [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) compatible function. | +| `cache` | `Cache` | `false` | Fetchye `Cache` object. *Defaults to `SimpleCache`* | #### `OneFetchyeProvider` @@ -1027,17 +1035,16 @@ import { OneFetchyeProvider } from 'fetchye-one-app'; **Context** -| name | type | required | description | -|---|---|---|---| -| `ReactReduxContext` | `ReactReduxContext` | `true` | A [Redux Context](https://react-redux.js.org/api/provider) from a ``. | +| name | type | required | description | +|---------------------|---------------------|----------|-----------------------------------------------------------------------------------| +| `ReactReduxContext` | `ReactReduxContext` | `true` | A [Redux Context](https://react-redux.js.org/api/provider) from a ``. | **Props** -| name | type | required | description | -|---|---|---|---| -| `fetchClient` | `ES6Fetch` | `true` | A [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) compatible function. | -| `cache` | `Cache` | `false` | Fetchye `Cache` object. *Defaults to `OneCache`* | - +| name | type | required | description | +|---------------|------------|----------|------------------------------------------------------------------------------------------------| +| `fetchClient` | `ES6Fetch` | `true` | A [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) compatible function. | +| `cache` | `Cache` | `false` | Fetchye `Cache` object. *Defaults to `OneCache`* | ### Caches @@ -1059,17 +1066,17 @@ const cache = SimpleCache({ **Arguments** -| name | type | required | description | -|---|---|---|---| -| `cacheSelector` | `(state) => state` | `false` | *Required if using `FetchyeReduxProvider`* A function that returns the location inside Redux State to the Fetchye Cache. (See [Redux Selectors](https://redux.js.org/recipes/computing-derived-data)). | +| name | type | required | description | +|-----------------|--------------------|----------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `cacheSelector` | `(state) => state` | `false` | *Required if using `FetchyeReduxProvider`* A function that returns the location inside Redux State to the Fetchye Cache. (See [Redux Selectors](https://redux.js.org/recipes/computing-derived-data)). | **Returns** -| name | type | description | -|---|---|---| -| `reducer` | `(state, action) => state` | A function that reduces the next state of Fetchye Cache. (See [Redux Reducers](https://redux.js.org/basics/reducers)). | -| `getCacheByKey` | `(cache, key) => state` | A function that returns a minimum of `{ data, loading, error }` for a specific cache key from cache state. | -| `cacheSelector?` | `(state) => state` | An optionally returned parameter. This function returns the location inside Redux State to the Fetchye Cache. (See [Redux Selectors](https://redux.js.org/recipes/computing-derived-data)). | +| name | type | description | +|------------------|----------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `reducer` | `(state, action) => state` | A function that reduces the next state of Fetchye Cache. (See [Redux Reducers](https://redux.js.org/basics/reducers)). | +| `getCacheByKey` | `(cache, key) => state` | A function that returns a minimum of `{ data, loading, error }` for a specific cache key from cache state. | +| `cacheSelector?` | `(state) => state` | An optionally returned parameter. This function returns the location inside Redux State to the Fetchye Cache. (See [Redux Selectors](https://redux.js.org/recipes/computing-derived-data)). | #### `ImmutableCache` @@ -1089,17 +1096,17 @@ const cache = ImmutableCache({ **Arguments** -| name | type | required | description | -|---|---|---|--| -| `cacheSelector` | `(state) => state` | `false` | *Required if using `FetchyeReduxProvider`* A function that returns the location inside Redux State to the Fetchye Cache. (See [Redux Selectors](https://redux.js.org/recipes/computing-derived-data)). | +| name | type | required | description | +|-----------------|--------------------|----------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `cacheSelector` | `(state) => state` | `false` | *Required if using `FetchyeReduxProvider`* A function that returns the location inside Redux State to the Fetchye Cache. (See [Redux Selectors](https://redux.js.org/recipes/computing-derived-data)). | **Returns** -| name | type | description | -|---|---|---| -| `reducer` | `(state, action) => state` | A function that reduces the next state of Fetchye Cache. (See [Redux Reducers](https://redux.js.org/basics/reducers)). | -| `getCacheByKey` | `(cache = Immutable.Map(), key) => state` | A function that returns a minimum of `{ data, loading, error }` for a specific cache key from cache state. | -| `cacheSelector?` | `(state) => state` | An optionally returned parameter. This function returns the location inside Redux State to the Fetchye Cache. (See [Redux Selectors](https://redux.js.org/recipes/computing-derived-data)). | +| name | type | description | +|------------------|-------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `reducer` | `(state, action) => state` | A function that reduces the next state of Fetchye Cache. (See [Redux Reducers](https://redux.js.org/basics/reducers)). | +| `getCacheByKey` | `(cache = Immutable.Map(), key) => state` | A function that returns a minimum of `{ data, loading, error }` for a specific cache key from cache state. | +| `cacheSelector?` | `(state) => state` | An optionally returned parameter. This function returns the location inside Redux State to the Fetchye Cache. (See [Redux Selectors](https://redux.js.org/recipes/computing-derived-data)). | #### `OneCache` @@ -1117,12 +1124,11 @@ const cache = OneCache(); **Returns** -| name | type | description | -|---|---|---| -| `reducer` | `(state, action) => state` | A function that reduces the next state of Fetchye Cache. (See [Redux Reducers](https://redux.js.org/basics/reducers)). | -| `getCacheByKey` | `(cache = Immutable.Map(), key) => state` | A function that returns a minimum of `{ data, loading, error }` for a specific cache key from cache state. | -| `cacheSelector?` | `(state) => state` | An optionally returned parameter. This function returns the location inside Redux State to the Fetchye Cache. (See [Redux Selectors](https://redux.js.org/recipes/computing-derived-data)). | - +| name | type | description | +|------------------|-------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `reducer` | `(state, action) => state` | A function that reduces the next state of Fetchye Cache. (See [Redux Reducers](https://redux.js.org/basics/reducers)). | +| `getCacheByKey` | `(cache = Immutable.Map(), key) => state` | A function that returns a minimum of `{ data, loading, error }` for a specific cache key from cache state. | +| `cacheSelector?` | `(state) => state` | An optionally returned parameter. This function returns the location inside Redux State to the Fetchye Cache. (See [Redux Selectors](https://redux.js.org/recipes/computing-derived-data)). | ### Actions @@ -1130,8 +1136,7 @@ These actions power the state transitions described in a Cache Configuration `re #### `IS_LOADING` -An event signaling a state transition to the loading state -by hash key. +An event signaling a state transition to the loading state by hash key. **Shape** @@ -1148,14 +1153,13 @@ import { IS_LOADING } from 'fetchye-core'; **Child Properties** -| name | type | description | -|---|---|---| +| name | type | description | +|--------|----------|----------------------------------------------------------------------------------------------------------| | `hash` | `String` | The hash value generated by [`object-hash`](https://github.com/puleos/object-hash) package for the query | #### `SET_DATA` -An event signaling a state transition inside a reducer to add or replace the data -field and transition away from loading state by hash key. +An event signaling a state transition inside a reducer to add or replace the data field and transition away from loading state by hash key. **Shape** @@ -1173,10 +1177,10 @@ import { SET_DATA } from 'fetchye-core'; **Child Properties** -| name | type | description | -|---|---|---| -| `hash` | `String` | The hash value generated by [`object-hash`](https://github.com/puleos/object-hash) package for the query | -| `value` | `Object` | Contains the `payload` data returned from the `fetcher` inside `useFetchye` | +| name | type | description | +|---------|----------|----------------------------------------------------------------------------------------------------------| +| `hash` | `String` | The hash value generated by [`object-hash`](https://github.com/puleos/object-hash) package for the query | +| `value` | `Object` | Contains the `payload` data returned from the `fetcher` inside `useFetchye` | #### `DELETE_DATA` @@ -1197,8 +1201,8 @@ import { DELETE_DATA } from 'fetchye-core'; **Child Properties** -| name | type | description | -|---|---|---| +| name | type | description | +|--------|----------|----------------------------------------------------------------------------------------------------------| | `hash` | `String` | The hash value generated by [`object-hash`](https://github.com/puleos/object-hash) package for the query | #### `ERROR` @@ -1221,10 +1225,10 @@ import { ERROR } from 'fetchye-core'; **Child Properties** -| name | type | description | -|---|---|---| -| `hash` | `String` | The hash value generated by [`object-hash`](https://github.com/puleos/object-hash) package for the query | -| `error` | `Error | String | null` | Contains the `error` value returned from the `fetcher` inside `useFetchye` | +| name | type | description | +|---------|----------|----------------------------------------------------------------------------------------------------------| +| `hash` | `String` | The hash value generated by [`object-hash`](https://github.com/puleos/object-hash) package for the query | +| `error` | `Error | String | null` | Contains the `error` value returned from the `fetcher` inside `useFetchye` | #### `CLEAR_ERROR` @@ -1245,11 +1249,12 @@ import { CLEAR_ERROR } from 'fetchye-core'; **Child Properties** -| name | type | description | -|---|---|---| +| name | type | description | +|--------|----------|----------------------------------------------------------------------------------------------------------| | `hash` | `String` | The hash value generated by [`object-hash`](https://github.com/puleos/object-hash) package for the query | ### mapOptionToKey Helpers + These helpers provide a more compact and simple way of common transforms. There is currently one helper. @@ -1262,9 +1267,9 @@ There is currently one helper. **Arguments** -| name | type | required | description | -|--------|-----------------|----------|--------------------------------------------------------------------------------------------------| -| `keys` | `Array` | `true` | creates a mapOptionsToKey function that removes headers whose keys match the specified keys | +| name | type | required | description | +|--------|-----------------|----------|---------------------------------------------------------------------------------------------| +| `keys` | `Array` | `true` | creates a mapOptionsToKey function that removes headers whose keys match the specified keys | **Returns** @@ -1291,38 +1296,20 @@ const BookList = ({ locale }) => { export default BookList; ``` - - - - ## ๐Ÿ“ข Mission -The Fetchye project wishes to bring a more flexible central caching experience -using the best ideas of the Redux design pattern and options for the developer -to choose how their data is stored. Fetchye provides React Context driven -caching options backed by your choice of pure React (via `useReducer`) or Redux. -Unlike many data fetching solutions, Fetchye Context Providers do not rely on -singleton statics and can be instantiated throughout an application multiple -times for multiple caches if so desired. +The Fetchye project wishes to bring a more flexible central caching experience using the best ideas of the Redux design pattern and options for the developer to choose how their data is stored. Fetchye provides React Context driven caching options backed by your choice of pure React (via `useReducer`) or Redux. Unlike many data fetching solutions, Fetchye Context Providers do not rely on singleton statics and can be instantiated throughout an application multiple times for multiple caches if so desired. ## ๐Ÿ† Contributing -We welcome Your interest in the American Express Open Source Community on Github. -Any Contributor to any Open Source Project managed by the American Express Open -Source Community must accept and sign an Agreement indicating agreement to the -terms below. Except for the rights granted in this Agreement to American Express -and to recipients of software distributed by American Express, You reserve all -right, title, and interest, if any, in and to Your Contributions. Please [fill -out the Agreement](https://cla-assistant.io/americanexpress/fetchye). +We welcome Your interest in the American Express Open Source Community on Github. Any Contributor to any Open Source Project managed by the American Express Open Source Community must accept and sign an Agreement indicating agreement to the terms below. Except for the rights granted in this Agreement to American Express and to recipients of software distributed by American Express, You reserve all right, title, and interest, if any, in and to Your Contributions. Please [fill out the Agreement](https://cla-assistant.io/americanexpress/fetchye). Please feel free to open pull requests and see [CONTRIBUTING.md](./CONTRIBUTING.md) to learn how to get started contributing. ## ๐Ÿ—๏ธ License -Any contributions made under this project will be governed by the [Apache License -2.0](./LICENSE.txt). +Any contributions made under this project will be governed by the [Apache License 2.0](./LICENSE.txt). ## ๐Ÿ—ฃ๏ธ Code of Conduct -This project adheres to the [American Express Community Guidelines](./CODE_OF_CONDUCT.md). -By participating, you are expected to honor these guidelines. +This project adheres to the [American Express Community Guidelines](./CODE_OF_CONDUCT.md). By participating, you are expected to honor these guidelines. diff --git a/packages/fetchye/__tests__/useFetchye.spec.jsx b/packages/fetchye/__tests__/useFetchye.spec.jsx index ab34fbc..09453ff 100644 --- a/packages/fetchye/__tests__/useFetchye.spec.jsx +++ b/packages/fetchye/__tests__/useFetchye.spec.jsx @@ -115,6 +115,7 @@ describe('useFetchye', () => { }); it('should call fetch with the right headers when passed dynamic headers', async () => { let fetchyeRes; + let dynamicValueCount = 0; global.fetch = jest.fn(async () => ({ ...defaultPayload, })); @@ -123,7 +124,11 @@ describe('useFetchye', () => { {React.createElement(() => { fetchyeRes = useFetchye('http://example.com', { headers: () => ({ - dynamicHeader: 'dynamic value', + dynamicHeader: `dynamic value ${dynamicValueCount}`, + }), + mapOptionsToKey: (options) => ({ + ...options, + dynamicHeader: null, }), }); return null; @@ -131,18 +136,21 @@ describe('useFetchye', () => { ); await waitFor(() => fetchyeRes.isLoading === false); - expect(global.fetch.mock.calls).toMatchInlineSnapshot(` - Array [ - Array [ - "http://example.com", - Object { - "headers": Object { - "dynamicHeader": "dynamic value", - }, - }, - ], - ] - `); + dynamicValueCount += 1; + await fetchyeRes.run(); + expect(global.fetch).toHaveBeenCalledTimes(2); + expect(global.fetch).toHaveBeenNthCalledWith(1, 'http://example.com', { + headers: { + dynamicHeader: 'dynamic value 0', + }, + mapOptionsToKey: expect.any(Function), + }); + expect(global.fetch).toHaveBeenNthCalledWith(2, 'http://example.com', { + headers: { + dynamicHeader: 'dynamic value 1', + }, + mapOptionsToKey: expect.any(Function), + }); }); it('should return data success state when response is empty (204 no content)', async () => { let fetchyeRes; diff --git a/packages/fetchye/src/makeServerFetchye.js b/packages/fetchye/src/makeServerFetchye.js index 25053b1..4e9b61b 100644 --- a/packages/fetchye/src/makeServerFetchye.js +++ b/packages/fetchye/src/makeServerFetchye.js @@ -33,7 +33,7 @@ const makeServerFetchye = ({ const computedKey = computeKey(key, options); const run = () => runAsync({ dispatch, - computedKey, + computedKey: computeKey(key, options), fetcher, fetchClient, options, diff --git a/packages/fetchye/src/useFetchye.js b/packages/fetchye/src/useFetchye.js index 8280055..e523a08 100644 --- a/packages/fetchye/src/useFetchye.js +++ b/packages/fetchye/src/useFetchye.js @@ -77,7 +77,7 @@ const useFetchye = ( run() { return runAsync({ dispatch, - computedKey, + computedKey: computeKey(key, options), fetcher: selectedFetcher, fetchClient, options, From a5cb219bde4ef3b1f1c15784dbb8b3e01a230525 Mon Sep 17 00:00:00 2001 From: Joshua James Allwood Date: Tue, 13 Feb 2024 16:31:06 +0000 Subject: [PATCH 4/4] fix(README): lint --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 28cacc8..46a083e 100644 --- a/README.md +++ b/README.md @@ -478,7 +478,7 @@ const BookList = ({ genre }) => { const { isLoading, data: booksData, - run + run, } = useFetchye(`http://example.com/api/books/?genre=${genre}`); if (isLoading) {