Skip to content

Some breakage with new 3.0 beta #56

Open
@xdave

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?

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions