diff --git a/README.md b/README.md index 261403c..20ec8f4 100644 --- a/README.md +++ b/README.md @@ -146,6 +146,8 @@ The body of the API call. `redux-api-middleware` uses the [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) to make the API call. `[RSAA].body` should hence be a valid body according to the [fetch specification](https://fetch.spec.whatwg.org). In most cases, this will be a JSON-encoded string or a [`FormData`](https://developer.mozilla.org/en/docs/Web/API/FormData) object. +It may also be a function taking the state of your Redux store as its argument, and returning a body as described above. + #### `[RSAA].headers` The HTTP headers for the API call. @@ -218,7 +220,7 @@ The `[RSAA].types` property controls the output of `redux-api-middleware`. The s - `type`: the string constant in the first position of the `[RSAA].types` array. But errors may pop up at this stage, for several reasons: - - `redux-api-middleware` has to call those of `[RSAA].bailout`, `[RSAA].endpoint` and `[RSAA].headers` that happen to be a function, which may throw an error; + - `redux-api-middleware` has to call those of `[RSAA].bailout`, `[RSAA].endpoint`, `[RSAA].body`, `[RSAA].options` and `[RSAA].headers` that happen to be a function, which may throw an error; - `fetch` may throw an error: the RSAA definition is not strong enough to preclude that from happening (you may, for example, send in a `[RSAA].body` that is not valid according to the fetch specification — mind the SHOULDs in the [RSAA definition](#redux-standard-api-calling-actions)); - a network failure occurs (the network is unreachable, the server responds with an error,...). @@ -577,7 +579,7 @@ The `[RSAA].method` property MUST be one of the strings `GET`, `HEAD`, `POST`, ` #### `[RSAA].body` -The optional `[RSAA].body` property SHOULD be a valid body according to the [fetch specification](https://fetch.spec.whatwg.org). +The optional `[RSAA].body` property SHOULD be a valid body according to the [fetch specification](https://fetch.spec.whatwg.org), or a function. In the second case, the function SHOULD return a valid body. #### `[RSAA].headers` diff --git a/src/middleware.js b/src/middleware.js index feb83e5..0077129 100644 --- a/src/middleware.js +++ b/src/middleware.js @@ -37,8 +37,8 @@ function apiMiddleware({ getState }) { // Parse the validated RSAA action const callAPI = action[RSAA]; - var { endpoint, headers, options = {} } = callAPI; - const { method, body, credentials, bailout, types } = callAPI; + var { endpoint, body, headers, options = {} } = callAPI; + const { method, credentials, bailout, types } = callAPI; const [requestType, successType, failureType] = normalizeTypeDescriptors( types ); @@ -82,6 +82,24 @@ function apiMiddleware({ getState }) { } } + // Process [RSAA].body function + if (typeof body === 'function') { + try { + body = body(getState()); + } catch (e) { + return next( + await actionWith( + { + ...requestType, + payload: new RequestError('[RSAA].body function failed'), + error: true + }, + [action, getState()] + ) + ); + } + } + // Process [RSAA].headers function if (typeof headers === 'function') { try { diff --git a/test/index.js b/test/index.js index 691fceb..d9d26a9 100644 --- a/test/index.js +++ b/test/index.js @@ -984,6 +984,48 @@ test('apiMiddleware must dispatch an error request FSA when [RSAA].bailout fails actionHandler(anAction); }); +test('apiMiddleware must dispatch an error request FSA when [RSAA].body fails', t => { + const anAction = { + [RSAA]: { + endpoint: 'http://127.0.0.1/api/users/1', + body: () => { + throw new Error(); + }, + method: 'GET', + types: [ + { + type: 'REQUEST', + payload: 'ignoredPayload', + meta: 'someMeta' + }, + 'SUCCESS', + 'FAILURE' + ] + } + }; + const doGetState = () => {}; + const nextHandler = apiMiddleware({ getState: doGetState }); + const doNext = action => { + t.pass('next handler called'); + t.equal(action.type, 'REQUEST', 'dispatched FSA has correct type property'); + t.equal( + action.payload.message, + '[RSAA].body function failed', + 'dispatched FSA has correct payload property' + ); + t.equal( + action.meta, + 'someMeta', + 'dispatched FSA has correct meta property' + ); + t.ok(action.error, 'dispatched FSA has correct error property'); + }; + const actionHandler = nextHandler(doNext); + + t.plan(5); + actionHandler(anAction); +}); + test('apiMiddleware must dispatch an error request FSA when [RSAA].endpoint fails', t => { const anAction = { [RSAA]: { @@ -1225,6 +1267,30 @@ test('apiMiddleware must use an [RSAA].bailout function when present', t => { actionHandler(anAction); }); +test('apiMiddleware must use an [RSAA].body function when present', t => { + const api = nock('http://127.0.0.1') + .get('/api/users/1') + .reply(200); + const anAction = { + [RSAA]: { + endpoint: 'http://127.0.0.1/api/users/1', + body: () => { + t.pass('[RSAA].body function called'); + return 'body'; + }, + method: 'GET', + types: ['REQUEST', 'SUCCESS', 'FAILURE'] + } + }; + const doGetState = () => {}; + const nextHandler = apiMiddleware({ getState: doGetState }); + const doNext = action => {}; + const actionHandler = nextHandler(doNext); + + t.plan(1); + actionHandler(anAction); +}); + test('apiMiddleware must use an [RSAA].endpoint function when present', t => { const api = nock('http://127.0.0.1') .get('/api/users/1')