drewwyatt
drewwyatt

Reputation: 6027

Typescript expects undefined to be passed as argument

Typescript: 2.4.1

I'm attempting to create a helper function to generate redux action creators.

What I've got:

interface IAction<T extends string, P = undefined> {
    type: T;
    payload: P;
}

function createAction<T extends string>(type: T): () => IAction<T, undefined>;
function createAction<T extends string = '', U = undefined, V = {}>(type: T, actionCreator: (payload: U) => V): (payload: U) => IAction<T, V>;
function createAction<T extends string>(type: T, actionCreator?: (payload?: any) => any) {
    return (payload: any) => ({
        payload,
        type,
    });
}

enum ActionType {
    INCREMENT = 'COUNTER/INCREMENT',
    DECREMENT = 'COUNTER/DECREMENT',
    ASYNC_INCREMENT = 'COUNTER/ASYNC_INCREMENT',
}

const increment = createAction(ActionType.INCREMENT, () => ({ amount: 1 }));
const incrementBy = createAction(ActionType.INCREMENT, (amount: number) => amount);

In this example, I expect increment to be a function that takes 0 arguments, and incrementBy to take exactly 1 (a number).

incrementBy works as expected, but Typescript throws this error when calling increment() (with no arguments):

[ts] Expected 1 arguments, but got 0;

Trying to call it the same way I call incrementBy, e.g. increment(42), I get this error:

[ts] Argument of type '42' is not assignable to parameter of type 'undefined';

This works: increment(undefined).

Is there a way to fix this?

Upvotes: 3

Views: 1876

Answers (1)

Rob
Rob

Reputation: 27377

You should simply create another overload which says it'll return a function with no arguments, rather than a function which takes undefined as the only argument.

function createAction<T extends string>(type: T): () => IAction<T, undefined>;
function createAction<T extends string, V>(type: T, actionCreator: () => V): () => IAction<T, V>;
function createAction<T extends string, U, V>(type: T, actionCreator: (payload: U) => V): (payload: U) => IAction<T, V>;
function createAction<T extends string, U, V>(type: T, actionCreator?: (payload: U) => V): (payload: U) => IAction<T, V> {
    return (payload: any) => ({
        payload,
        type,
    });
}

However, your implementation doesn't seem to do anything with the function being passed. You likely want to do this:

function createAction<T extends string>(type: T): () => IAction<T, undefined>;
function createAction<T extends string, V>(type: T, actionCreator: () => V): () => IAction<T, V>;
function createAction<T extends string, U, V>(type: T, actionCreator: (payload: U) => V): (payload: U) => IAction<T, V>;
function createAction<T extends string, U, V>(type: T, actionCreator?: (payload: U) => V): (payload: U) => IAction<T, V> {
    if (actionCreator) {
        return (payload: U) => ({
            payload: actionCreator(payload),
            type,
        });
    } else {
        return () => ({
            payload: undefined,
            type
        });
    }
}

Upvotes: 2

Related Questions