[redux-pack]: add key signature to ActionMeta interface (#24785)

* [redux-pack]: add key signature to ActionMeta interface

* [redux-pack]: improve type definitions for handler actions, allow passing custom metadata and all FSA compliant keys

* [redux-pack]: fix no-unnecessary-generics error
This commit is contained in:
Peter Weinberg 2018-04-11 19:31:22 -04:00 committed by Mohamed Hegazy
parent 326cb88142
commit d8df907997
2 changed files with 111 additions and 42 deletions

View File

@ -1,52 +1,78 @@
// Type definitions for redux-pack 0.1
// Project: https://github.com/lelandrichardson/redux-pack
// Definitions by: tansongyang <https://github.com/tansongyang>
// dschuman <https://github.com/quicksnap>
// pweinberg <https://github.com/no-stack-dub-sack>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
// TypeScript Version: 2.4
import {
Action as ReduxAction,
Middleware,
Reducer,
Reducer
} from 'redux';
export const KEY: {
readonly LIFECYCLE: 'redux-pack/LIFECYCLE'
readonly TRANSACTION: 'redux-pack/TRANSACTION'
readonly LIFECYCLE: 'redux-pack/LIFECYCLE';
readonly TRANSACTION: 'redux-pack/TRANSACTION';
};
export const LIFECYCLE: {
readonly START: 'start'
readonly SUCCESS: 'success'
readonly FAILURE: 'failure'
readonly START: 'start';
readonly SUCCESS: 'success';
readonly FAILURE: 'failure';
};
export type LIFECYCLEValues = 'start'| 'succes'| 'failure';
export type LIFECYCLEValues = 'start' | 'succes' | 'failure';
export const middleware: Middleware;
export interface Handlers<S> {
start?: Reducer<S>;
finish?: Reducer<S>;
failure?: Reducer<S>;
success?: Reducer<S>;
always?: Reducer<S>;
// MetaPayload differs from ActionMeta in that it is the object that the reducers
// receive, instead of what is dispatched
export type MetaPayload<M> = M & {
['redux-pack/LIFECYCLE']?: LIFECYCLEValues;
['redux-pack/TRANSACTION']?: string;
};
// Incomplete typing
export type PackActionPayload<Payload, M> = ReduxAction & {
payload: Payload;
meta: MetaPayload<M>;
};
export type handlerReducer<S, A> = (state: S, action: A) => S;
export interface Handlers<S, TSuccessPayload, TErrorPayload, TStartPayload, TMetaPayload> {
start?: handlerReducer<S, PackActionPayload<TStartPayload, TMetaPayload>>;
finish?: handlerReducer<S, ReduxAction>;
failure?: handlerReducer<S, PackActionPayload<TErrorPayload, TMetaPayload>>;
success?: handlerReducer<S, PackActionPayload<TSuccessPayload, TMetaPayload>>;
always?: handlerReducer<S, ReduxAction>;
}
export type GetState<S> = () => S;
export interface ActionMeta<TState = {}, TSuccessPayload = {}, TErrorPayload = {}, TStartPayload = {}> {
export interface ActionMeta<TFullState = {}, TSuccessPayload = {}, TErrorPayload = {}, TStartPayload = {}> {
startPayload?: TStartPayload;
onStart?(payload: TStartPayload, getState: GetState<TState>): void;
onFinish?(resolved: boolean, getState: GetState<TState>): void;
onSuccess?(response: TSuccessPayload, getState: GetState<TState>): void;
onFailure?(error: TErrorPayload, getState: GetState<TState>): void;
['redux-pack/LIFECYCLE']?: keyof LIFECYCLEValues;
onStart?(payload: TStartPayload, getState: GetState<TFullState>): void;
onFinish?(resolved: boolean, getState: GetState<TFullState>): void;
onSuccess?(response: TSuccessPayload, getState: GetState<TFullState>): void;
onFailure?(error: TErrorPayload, getState: GetState<TFullState>): void;
['redux-pack/LIFECYCLE']?: LIFECYCLEValues;
['redux-pack/TRANSACTION']?: string;
}
export interface Action<TState = {}, TSuccessPayload = {}, TErrorPayload = {}, TStartPayload = {}> extends ReduxAction {
export interface PackError { error: boolean; payload: any; }
export interface Action<TFullState = {}, TSuccessPayload = {}, TErrorPayload = PackError, TStartPayload = {}, TMetaPayload = {}> extends ReduxAction {
promise?: Promise<TSuccessPayload>;
payload?: TSuccessPayload | TErrorPayload | TStartPayload;
meta?: ActionMeta<TState, TSuccessPayload, TErrorPayload, TStartPayload>;
meta?: ActionMeta<TFullState, TSuccessPayload, TErrorPayload, TStartPayload> & TMetaPayload;
// add optional error key to conform to FSA design: https://github.com/redux-utilities/flux-standard-action
// note that users of this middleware (using our types) must conform to FSA shaped actions or code will not compile
error?: boolean | null;
}
export function handle<TState>(
export interface TFullState { [key: string]: any; }
export function handle<TState, TSuccessPayload, TErrorPayload, TStartPayload, TMetaPayload>(
state: TState,
action: Action<TState, any, any, any>,
handlers: Handlers<TState>)
: TState;
action: Action<TFullState, TSuccessPayload, TErrorPayload, TStartPayload, TMetaPayload>,
handlers: Handlers<TState, TSuccessPayload, TErrorPayload, TStartPayload, TMetaPayload>,
): TState;

View File

@ -1,44 +1,87 @@
import { handle, Action } from 'redux-pack';
import { handle, Action, GetState } from 'redux-pack';
interface Foo {
id: string;
id: string;
}
interface FooState {
foo: Foo | null;
error: string | null;
isLoading: boolean;
currentUser: {
id: string;
};
foo: Foo | null;
bar: string;
error: boolean;
errorMsg: string;
isLoading: boolean;
metaPropOne: string;
metaPropTwo: string;
currentUser: {
id: string;
};
}
// https://github.com/lelandrichardson/redux-pack/tree/v0.1.5#logging-beforeafter
declare const Api: {
getFoo(id: string): Promise<Foo>;
getFoo(id: string): Promise<Foo>;
};
declare function logSuccess(foo: Foo): void;
function loadFoo(id: string): Action<FooState, Foo> {
interface MetaOne { propOne: string; }
function loadFoo(id: string): Action<FooState, Foo, {}, {}, MetaOne> {
return {
type: LOAD_FOO,
promise: Api.getFoo(id),
meta: {
onSuccess: logSuccess
onSuccess: logSuccess,
// pass custom metadata through to reducer
// allowed by library, and a common redux pattern
// https://github.com/lelandrichardson/redux-pack/blob/7818ffd4304d5f0e2c94056f3626a399fc9a5a10/src/middleware.js#L44
propOne: 'some meta'
},
};
}
interface MetaTwo { propTwo: string; }
function barError(): Action<FooState, Foo, {}, {}, MetaTwo> {
return {
type: BAR_ERROR,
error: true,
payload: new Error('this is an error action'),
meta: {
propTwo: 'other meta'
}
};
}
// bad (non-FSA action):
function baz(baz: string): Action<FooState, Foo> {
return {
type: 'BAZ',
// boo: baz // <-- will not compile, shape of action is not FSA
// see line 62 & 63 of index.d.ts
};
}
// https://github.com/lelandrichardson/redux-pack/tree/v0.1.5#using-the-handle-helper
const LOAD_FOO = 'LOAD_FOO';
const BAR_ERROR = 'BAR_ERROR';
declare const initialState: FooState;
function fooReducer(state = initialState, action: Action<FooState, Foo, string | null>) {
function fooReducer(state = initialState, action: Action<FooState, Foo, string | null, {}, MetaOne & MetaTwo>) {
const { type, payload } = action;
switch (type) {
// example of non-redux-pack action
// works as long as action is FSA compliant
case BAR_ERROR:
return {
...state,
error: action.error,
errorMsg: action.payload,
metaPropTwo: action.meta && action.meta.propTwo
};
case LOAD_FOO:
return handle(state, action, {
start: prevState => ({ ...prevState, isLoading: true, error: null, foo: null }),
start: prevState => ({ ...prevState, isLoading: true, error: false, foo: null }),
finish: prevState => ({ ...prevState, isLoading: false }),
failure: prevState => ({ ...prevState, error: payload as string }),
success: prevState => ({ ...prevState, foo: payload as Foo }),
failure: prevState => ({ ...prevState, error: true, errorMsg: payload as string }),
// must define both state and action params to correctly scope action (to access custom meta)
success: (prevState, action) => ({ ...prevState, foo: payload as Foo, metaPropOne: action.meta.propOne }),
always: prevState => prevState, // unnecessary, for the sake of example
});
default:
@ -50,8 +93,8 @@ function fooReducer(state = initialState, action: Action<FooState, Foo, string |
const DO_FOO = 'DO_FOO';
declare function doFoo(): Promise<Foo>;
declare function sendAnalytics(action: string, data: {
userId: string,
fooId: string,
userId: string,
fooId: string,
}): void;
function userDoesFoo(): Action<FooState, Foo> {
return {