Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 20 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,26 +182,32 @@ The `[CALL_API].types` property controls the output of `redux-api-middleware`. T
1. When `redux-api-middleware` receives an action, it first checks whether it has a `[CALL_API]` property. If it does not, it was clearly not intended for processing with `redux-api-middleware`, and so it is unceremoniously passed on to the next middleware.

2. It is now time to validate the action against the [RSAA definition](#redux-standard-api-calling-actions). If there are any validation errors, a *request* FSA will be dispatched (if at all possible) with the following properties:
- `type`: the string constant in the first position of the `[CALL_API].types` array;
- `payload`: an [`InvalidRSAA`](#invalidrsaa) object containing a list of said validation errors;
- `error: true`.
- `type`: the string constant in the first position of the `[CALL_API].types` array;
- `payload`: an [`InvalidRSAA`](#invalidrsaa) object containing a list of said validation errors;
- `error: true`.

`redux-api-middleware` will perform no further operations. In particular, no API call will be made, and the incoming RSAA will die here.

3. Now that `redux-api-middleware` is sure it has received a valid RSAA, it will try making the API call. If everything is alright, a *request* FSA will be dispatched with the following property:
3. Next, `redux-api-middleware` prepares the API call and has to call those of `[CALL_API].bailout`, `[CALL_API].endpoint` and `[CALL_API].headers` that happen to be a function. If any of these throw an error, a *request* FSA will be dispatched with the following properties:
- `type`: the string constant in the first position of the `[CALL_API].types` array;
- `payload`: a [`RequestError`](#requesterror) object containing an error message;
- `error: true`.

Processing stops here and `redux-api-middleware` will perform no further operations. In particular, no API call will be made, and the incoming RSAA will die here.

4. Now that `redux-api-middleware` is sure it has received a valid RSAA, and has successfully prepared the API call, it will try making the API call. A *request* FSA will be dispatched with the following property:
- `type`: the string constant in the first position of the `[CALL_API].types` array.

But errors may pop up at this stage, for several reasons:
- `redux-api-middleware` has to call those of `[CALL_API].bailout`, `[CALL_API].endpoint` and `[CALL_API].headers` that happen to be a function, which may throw an error;
- `isomorphic-fetch` may throw an error: the RSAA definition is not strong enough to preclude that from happening (you may, for example, send in a `[CALL_API].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,...).

If such an error occurs, a different *request* FSA will be dispatched (*instead* of the one described above). It will contain the following properties:
- `type`: the string constant in the first position of the `[CALL_API].types` array;
If such an error occurs, a *failure* FSA will be dispatched (after the *request* FSA described above) and processing stops here. The FSA will contain the following properties:
- `type`: the string constant in the third position of the `[CALL_API].types` array;
- `payload`: a [`RequestError`](#requesterror) object containing an error message;
- `error: true`.

4. If `redux-api-middleware` receives a response from the server with a status code in the 200 range, a *success* FSA will be dispatched with the following properties:
5. If `redux-api-middleware` receives a response from the server with a status code in the 200 range, a *success* FSA will be dispatched with the following properties:
- `type`: the string constant in the second position of the `[CALL_API].types` array;
- `payload`: if the `Content-Type` header of the response is set to something JSONy (see [*Success* type descriptors](#success-type-descriptors) below), the parsed JSON response of the server, or undefined otherwise.

Expand Down Expand Up @@ -282,7 +288,7 @@ By default, *request* FSAs will not contain `payload` and `meta` properties.

Error *request* FSAs might need to obviate these custom settings though.
- *Request* FSAs resulting from invalid RSAAs (step 2 in [Lifecycle](#lifecycle) above) cannot be customized. `redux-api-middleware` will try to dispatch an error *request* FSA, but it might not be able to (it may happen that the invalid RSAA does not contain a value that can be used as the *request* FSA `type` property, in which case `redux-api-middleware` will let the RSAA die silently).
- *Request* FSAs resulting in request errors (step 3 in [Lifecycle](#lifecycle) above) will honor the user-provided `meta`, but will ignore the user-provided `payload`, which is reserved for the default error object.
- *Request* FSAs resulting from errors while preparing the API call (step 3 in [Lifecycle](#lifecycle) above) will honor the user-provided `meta`, but will ignore the user-provided `payload`, which is reserved for the default error object.

#### *Success* type descriptors

Expand Down Expand Up @@ -351,7 +357,7 @@ By default, *success* FSAs will not contain a `meta` property, while their `payl

#### *Failure* type descriptors

`payload` and `meta` functions will be passed the RSAA action itself, the state of your Redux store, and the raw server response — exactly as for *success* type descriptors. The `error` property of dispatched *failure* FSAs will always be set to `true`.
`payload` and `meta` functions will be passed the RSAA action itself, the state of your Redux store, and the raw server response if available. The `error` property of dispatched *failure* FSAs will always be set to `true`.

For example, if you want the status code and status message of a unsuccessful API call in the `meta` property of your *failure* FSA, do the following.

Expand Down Expand Up @@ -382,6 +388,7 @@ For example, if you want the status code and status message of a unsuccessful AP
}
}
```

By default, *failure* FSAs will not contain a `meta` property, while their `payload` property will be evaluated from
```js
(action, state, res) =>
Expand All @@ -390,6 +397,9 @@ By default, *failure* FSAs will not contain a `meta` property, while their `payl
)
```

*Failure* FSAs without a server response need to obviate these custom settings though.
- *Failure* FSAs resulting from network failures or errors thrown by `isomorphic-fetch` (step 4 in [Lifecycle](#lifecycle) above) will honor the user-provided `meta`, but will ignore the user-provided `payload`, which is reserved for the default error object.

## Reference

### Exports
Expand Down
4 changes: 3 additions & 1 deletion src/middleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ function apiMiddleware({ getState }) {
requestType,
[action, getState()]
));
// As we have dispatched the request FSA, all other
// dispatches must now be either success or failure

try {
// Make the API call
Expand All @@ -105,7 +107,7 @@ function apiMiddleware({ getState }) {
// The request was malformed, or there was a network error
return next(await actionWith(
{
...requestType,
...failureType,
payload: new RequestError(e.message),
error: true
},
Expand Down
97 changes: 50 additions & 47 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -1097,11 +1097,15 @@ test('apiMiddleware must dispatch an error request FSA on a request error', (t)
types: [
{
type: 'REQUEST',
payload: 'ignoredPayload',
meta: 'someMeta'
payload: 'someRequestPayload',
meta: 'someRequestMeta'
},
'SUCCESS',
'FAILURE'
{
type: 'FAILURE',
payload: 'ignoredFailurePayload',
meta: 'someFailureMeta'
}
]
}
};
Expand All @@ -1110,50 +1114,49 @@ test('apiMiddleware must dispatch an error request FSA on a request error', (t)
const doNext = (action) => {
switch (action.type) {
case 'REQUEST':
if (!action.error) {
t.pass('next handler called');
t.equal(
action.type,
'REQUEST',
'dispatched non-error FSA has correct type property'
);
t.equal(
action.payload,
'ignoredPayload',
'dispatched non-error FSA has correct payload property'
);
t.equal(
action.meta,
'someMeta',
'dispatched non-error FSA has correct meta property'
);
t.notOk(
action.error,
'dispatched non-error FSA has correct error property'
);
break;
} else {
t.pass('next handler called');
t.equal(
action.type,
'REQUEST',
'dispatched error FSA has correct type property'
);
t.equal(
action.payload.name,
'RequestError',
'dispatched error FSA has correct payload property'
);
t.equal(
action.meta,
'someMeta',
'dispatched error FSA has correct meta property'
);
t.ok(
action.error,
'dispatched error FSA has correct error property'
);
}
t.pass('next handler called');
t.equal(
action.type,
'REQUEST',
'dispatched request FSA has correct type property'
);
t.equal(
action.payload,
'someRequestPayload',
'dispatched request FSA has correct payload property'
);
t.equal(
action.meta,
'someRequestMeta',
'dispatched request FSA has correct meta property'
);
t.notOk(
action.error,
'dispatched request FSA has correct error property'
);
break;
case 'FAILURE':
t.pass('next handler called');
t.equal(
action.type,
'FAILURE',
'dispatched failure FSA has correct type property'
);
t.equal(
action.payload.name,
'RequestError',
'dispatched failure FSA has correct payload property'
);
t.equal(
action.meta,
'someFailureMeta',
'dispatched failure FSA has correct meta property'
);
t.ok(
action.error,
'dispatched failure FSA has correct error property'
);
break;
}
};
const actionHandler = nextHandler(doNext);
Expand Down