diff --git a/types/redux-pack/index.d.ts b/types/redux-pack/index.d.ts index 2bffdf8117..01e6ff4c0d 100644 --- a/types/redux-pack/index.d.ts +++ b/types/redux-pack/index.d.ts @@ -1,52 +1,78 @@ // Type definitions for redux-pack 0.1 // Project: https://github.com/lelandrichardson/redux-pack // Definitions by: tansongyang +// dschuman +// pweinberg // 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 { - start?: Reducer; - finish?: Reducer; - failure?: Reducer; - success?: Reducer; - always?: Reducer; +// MetaPayload differs from ActionMeta in that it is the object that the reducers +// receive, instead of what is dispatched +export type MetaPayload = M & { + ['redux-pack/LIFECYCLE']?: LIFECYCLEValues; + ['redux-pack/TRANSACTION']?: string; +}; + +// Incomplete typing +export type PackActionPayload = ReduxAction & { + payload: Payload; + meta: MetaPayload; +}; + +export type handlerReducer = (state: S, action: A) => S; +export interface Handlers { + start?: handlerReducer>; + finish?: handlerReducer; + failure?: handlerReducer>; + success?: handlerReducer>; + always?: handlerReducer; } + export type GetState = () => S; -export interface ActionMeta { +export interface ActionMeta { startPayload?: TStartPayload; - onStart?(payload: TStartPayload, getState: GetState): void; - onFinish?(resolved: boolean, getState: GetState): void; - onSuccess?(response: TSuccessPayload, getState: GetState): void; - onFailure?(error: TErrorPayload, getState: GetState): void; - ['redux-pack/LIFECYCLE']?: keyof LIFECYCLEValues; + onStart?(payload: TStartPayload, getState: GetState): void; + onFinish?(resolved: boolean, getState: GetState): void; + onSuccess?(response: TSuccessPayload, getState: GetState): void; + onFailure?(error: TErrorPayload, getState: GetState): void; + ['redux-pack/LIFECYCLE']?: LIFECYCLEValues; ['redux-pack/TRANSACTION']?: string; } -export interface Action extends ReduxAction { + +export interface PackError { error: boolean; payload: any; } +export interface Action extends ReduxAction { promise?: Promise; payload?: TSuccessPayload | TErrorPayload | TStartPayload; - meta?: ActionMeta; + meta?: ActionMeta & 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( + +export interface TFullState { [key: string]: any; } +export function handle( state: TState, - action: Action, - handlers: Handlers) - : TState; + action: Action, + handlers: Handlers, +): TState; diff --git a/types/redux-pack/redux-pack-tests.ts b/types/redux-pack/redux-pack-tests.ts index ff1746bdbf..0dbe1bf433 100644 --- a/types/redux-pack/redux-pack-tests.ts +++ b/types/redux-pack/redux-pack-tests.ts @@ -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; + getFoo(id: string): Promise; }; + declare function logSuccess(foo: Foo): void; -function loadFoo(id: string): Action { + +interface MetaOne { propOne: string; } +function loadFoo(id: string): Action { 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 { + 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 { + 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) { +function fooReducer(state = initialState, action: Action) { 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; declare function sendAnalytics(action: string, data: { - userId: string, - fooId: string, + userId: string, + fooId: string, }): void; function userDoesFoo(): Action { return {