Description
My package, typescript-fsa-redux-thunk
and my attempts to refactor it have failed, and this also affects typescript-fsa-reducers
. The best way to explain the errors I'm getting is to post a test case and explain where the errors are:
import {
Dispatch,
applyMiddleware,
bindActionCreators,
createStore
} from 'redux';
import thunk, { ThunkAction } from 'redux-thunk';
import actionCreatorFactory, {
AsyncActionCreators,
ActionCreatorFactory
} from 'typescript-fsa';
import { reducerWithInitialState } from 'typescript-fsa-reducers';
/**
* It's either a promise, or it isn't
*/
type MaybePromise<T> = T | Promise<T>;
/**
* A redux-thunk with the params as the first argument. You don't have to
* return a promise; but, the result of the dispatch will be one.
*/
export type AsyncWorker<State, Params, Result, Extra = any> = (
params: Params,
dispatch: Dispatch<State>,
getState: () => State,
extra: Extra
) => MaybePromise<Result>;
/**
* Bind a redux-thunk to typescript-fsa async action creators
* @param actionCreators - The typescript-fsa async action creators
* @param asyncWorker - The redux-thunk with the params as the first argument
* @returns a regular redux-thunk you can pass to dispatch()
*/
export const bindThunkAction = <State, P, S, E, ExtraArg = any>(
actionCreators: AsyncActionCreators<P, S, E>,
asyncWorker: AsyncWorker<State, P, S, ExtraArg>
) => (
params: P
): ThunkAction<Promise<S>, State, ExtraArg> => (
dispatch,
getState,
extra
) => {
dispatch(actionCreators.started(params));
return Promise.resolve(asyncWorker(params, dispatch, getState, extra))
.then(result => {
dispatch(actionCreators.done({ params, result }));
return result;
})
.catch((error: E) => {
dispatch(actionCreators.failed({ params, error }));
throw error;
});
};
/**
* Factory function to easily create a typescript-fsa redux thunk
* @param factory - typescript-fsa action creator factory
* @returns an object with the async actions and the thunk itself
*/
export const asyncFactory = <State, E = Error, ExtraArg = any>(
factory: ActionCreatorFactory
) => <P, S>(type: string, fn: AsyncWorker<State, P, S, ExtraArg>) => {
const actions = factory.async<P, S, E>(type);
return {
async: actions,
action: bindThunkAction(actions, fn)
};
};
/**
* Passing the result of this to bindActionCreators and then calling the result
* is equivalent to calling `store.dispatch(thunkAction(params))`. Useful for
* when you pass it to `connect()` as the actionCreators map object.
* @param thunkAction - The thunk action
* @returns thunkAction as if it was bound
*/
export const thunkToAction = <P, R, S, ExtraArg>(
thunkAction: (params: P) => ThunkAction<R, S, ExtraArg>
): ((params: P) => R) => thunkAction as any;
/****** TEST CODE ******/
interface SomeState {
hmm: number;
}
const create = actionCreatorFactory('something');
const createAsync = asyncFactory<SomeState>(create);
const someAction = create<string>('SOME_ACTION');
const test = createAsync<{ arg: string; }, number>(
'ASYNC_ACTION',
async params => {
console.log(params);
return 100;
}
);
const initial: SomeState = {
hmm: 0
};
const reducer = reducerWithInitialState(initial)
.case(someAction, state => state)
.case(test.async.started, state => state)
.case(test.async.failed, state => state)
.case(test.async.done, (state, { result }) => ({
...state,
hmm: result
}));
if (module === require.main) {
const store = createStore(reducer, applyMiddleware(thunk));
const actions = bindActionCreators({
test: thunkToAction(test.action)
}, store.dispatch);
actions.test({ arg: 'test' })
.then(result => console.log(result))
.catch(err => console.log(err));
}
There are only three errors, and interestingly, all occurring inside of my bindThunkAction
function where the async actions are called:
// Line 46:
dispatch(actionCreators.started(params));
// Cannot invoke an expression whose type lacks a call signature. Type '({ type: string; match: (action: AnyAction) => action is Action<P>; } & ((payload?: P | undefined...' has no compatible call signatures.
// Line 49:
dispatch(actionCreators.done({ params, result }));
// Argument of type '{ params: P; result: S; }' is not assignable to parameter of type 'Optionalize<{ params: P; result: S; }>'.
// Type '{ params: P; result: S; }' is not assignable to type '{ [P in (P extends undefined ? never : "params") | (S extends undefined ? never : "result")]: { p...'.
// Line 53:
dispatch(actionCreators.failed({ params, error }));
// Argument of type '{ params: P; error: E; }' is not assignable to parameter of type 'Optionalize<{ params: P; error: E; }>'.
// Type '{ params: P; error: E; }' is not assignable to type '{ [P in (P extends undefined ? never : "params") | (E extends undefined ? never : "error")]: { pa...'.
I've been fiddling with my own code for hours, and I can't seem to figure out why this is happening. Passing normal parameters to these action creators outside of bindThunkAction
works fine.
Another weirdness: even with these errors, the reducer seems to work; but, the types of params
and result
, etc have a weird optional-like, for example: number | { undefined & number }
.
If one ignores the errors and runs the code with ts-node
, it executes without errors. Am I losing it?