Improve redux-api-middleware dispatch typings (#38645)

Rehauled the dispatched actions to be typed using Discriminated Union.
The promise results are now correctly typed without needing explicit typing.
This commit is contained in:
Arturs Vonda 2019-10-04 00:40:35 +03:00 committed by Ryan Cavanaugh
parent 09169a799d
commit 928870246a
2 changed files with 122 additions and 39 deletions

View File

@ -2,16 +2,11 @@
// Project: https://github.com/agraboso/redux-api-middleware
// Definitions by: Andrew Luca <https://github.com/iamandrewluca>
// Craig S <https://github.com/Mrman>
// Arturs Vonda <https://github.com/artursvonda>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
// TypeScript Version: 3.0
import {
Middleware,
MiddlewareAPI,
Dispatch,
AnyAction,
applyMiddleware
} from 'redux';
import { Dispatch, Middleware, MiddlewareAPI } from 'redux';
type TypeOrResolver<Arg, Type> = Type | ((arg: Arg) => Type);
@ -121,32 +116,36 @@ export interface RSAAAction<State = any, Payload = any, Meta = any> {
[RSAA]: RSAACall<State, Payload, Meta>;
}
type ValidAction<Payload = never, Meta = never> =
{ type: string | symbol; error?: false }
// The `[Payload] extends [never]` is required to check if generic type is never.
// Can't do it with just `Payload extends never`.
& ([Payload] extends [never] ? {} : { payload: Payload })
& ([Meta] extends [never] ? {} : { meta: Meta });
interface InvalidAction<Payload> {
type: string | symbol;
payload: Payload;
error: true;
}
/**
* `Promise<RSAARequestAction>` is not returned from dispatch like other actions
* Is only dispatched through redux
*/
export interface RSAARequestAction<Payload = any, Meta = any> {
type: string | symbol;
payload?: Payload | InvalidRSAA;
meta?: Meta;
error?: true;
}
export type RSAARequestAction<Payload = never, Meta = never> = ValidAction<Payload, Meta> | InvalidAction<InvalidRSAA>;
export interface RSAASuccessAction<Payload = any, Meta = any> {
type: string | symbol;
payload: Payload | InternalError;
meta?: Meta;
error?: true;
}
// @deprecated Use RSAAResultAction
export type RSAASuccessAction<Payload = any, Meta = any> = RSAAResultAction<Payload, Meta>;
export interface RSAAFailureAction<Payload = any, Meta = any> {
type: string | symbol;
payload: InternalError | RequestError | ApiError<Payload>;
meta?: Meta;
error: true;
}
// @deprecated Use RSAAResultAction
export type RSAAFailureAction<Payload = any, Meta = any> = RSAAResultAction<Payload, Meta>;
export type RSAAActions = RSAARequestAction | RSAASuccessAction | RSAAFailureAction;
export type RSAAResultAction<Payload = never, Meta = never> =
| ValidAction<Payload, Meta>
| InvalidAction<InternalError | RequestError | ApiError<Payload>>;
export type RSAAActions = RSAARequestAction | RSAAResultAction;
/**
* Redux behaviour changed by middleware, so overloads here
@ -157,8 +156,7 @@ declare module 'redux' {
* Useful for react-redux or any other library which could use this type.
*/
interface Dispatch {
(action: RSAAAction): Promise<RSAASuccessAction>;
(action: RSAAAction): Promise<RSAAFailureAction>;
<Payload, Meta>(action: RSAAAction<any, Payload, Meta>): Promise<RSAAResultAction<Payload, Meta>>;
// `Promise<undefined> is returned in case of RSAA validation errors or user bails out
(action: RSAAAction): Promise<undefined>;
}

View File

@ -17,8 +17,7 @@ import {
RSAASuccessTypeDescriptor,
RSAAFailureTypeDescriptor,
RSAARequestAction,
RSAASuccessAction,
RSAAFailureAction,
RSAAResultAction,
} from 'redux-api-middleware';
{
@ -156,7 +155,7 @@ import {
{
const store: Store = createStore(() => undefined);
const action: RSAAAction = {
const action: RSAAAction<any, string, number> = {
[RSAA]: {
endpoint: '/test/endpoint',
method: 'GET',
@ -165,10 +164,21 @@ import {
};
store.dispatch(action);
store.dispatch(action).then((action: RSAASuccessAction) => Promise.resolve());
store.dispatch(action).then((action: RSAAResultAction<string, number>) => Promise.resolve());
store.dispatch(action).then(() => Promise.resolve());
store
.dispatch(action)
.then(action => (action.error ? Promise.reject() : Promise.resolve(action.payload)))
.then((payload: string) => payload);
store
.dispatch(action)
.then(action => (action.error ? Promise.reject() : Promise.resolve(action.meta)))
.then((payload: number) => Promise.resolve());
store
.dispatch(action)
.then(action => (action.error ? Promise.reject() : Promise.resolve(action.payload)))
.then((payload: number) => Promise.resolve()); // $ExpectError
store.dispatch(action).then((action: string) => Promise.resolve()); // $ExpectError
store.dispatch(action).catch((action: RSAAFailureAction) => Promise.reject());
}
{
@ -222,18 +232,93 @@ import {
}
{
const requestAction0: RSAARequestAction<number, number> = {
type: ''
const requestActionSimple: RSAARequestAction = {
type: '',
};
const successAction0: RSAASuccessAction<number, number> = {
const requestActionWithPayload: RSAARequestAction<number> = {
type: '',
payload: 6
payload: 1,
};
const failureAction0: RSAAFailureAction<number, number> = {
const requestActionWithIncorrectPayload: RSAARequestAction<number> = {
type: '',
payload: '', // $ExpectError
};
const requestActionWithMeta: RSAARequestAction<never, number> = {
type: '',
meta: 1,
};
// $ExpectError
const requestActionWithMissingPayload: RSAARequestAction<number> = {
type: '',
};
const requestActionWithExtraneousMeta: RSAARequestAction<number> = {
type: '',
payload: 1,
meta: 1, // $ExpectError
};
const requestActionWithError: RSAARequestAction<number> = {
type: '',
error: true,
payload: new InvalidRSAA(['']),
};
const resultActionSimple: RSAAResultAction = {
type: '',
};
const resultActionWithPayload: RSAAResultAction<number> = {
type: '',
payload: 1,
};
const resultActionWithIncorrectPayload: RSAAResultAction<number> = {
type: '',
payload: '', // $ExpectError
};
const resultActionWithMeta: RSAAResultAction<never, number> = {
type: '',
meta: 1,
};
// $ExpectError
const resultActionWithMissingPayload: RSAAResultAction<number> = {
type: '',
};
const resultActionWithExtraneousMeta: RSAAResultAction<number> = {
type: '',
payload: 1,
meta: 1, // $ExpectError
};
const resultActionWithApiError: RSAAResultAction<number> = {
type: '',
error: true,
payload: new ApiError(500, '', 1),
error: true
};
const resultActionWithInternalError: RSAAResultAction<number> = {
type: '',
error: true,
payload: new InternalError(''),
};
const resultActionWithRequestError: RSAAResultAction<number> = {
type: '',
error: true,
payload: new RequestError(''),
};
// $ExpectError
const resultActionWithMissingErrorPayload: RSAAResultAction<number> = {
type: '',
error: true,
};
}