2

I'm trying to DRY up some of my redux action creators but am having a hard time with coming up with the proper types. Take this simplified example:

export interface SortCollectionAction {
    type: string
    payload: {
        field: string
        direction: string
    }
}

export function sortCollection(type: string, field: string, direction: string): SortCollectionAction {
    return {
        type: type,
        payload: {
            field: field,
            direction: direction
        }
    }
}

const LIST_SORT = 'LIST_SORT'

interface SortListAction extends SortCollectionAction {
    type: typeof LIST_SORT
}

function sortList(field: string, direction: string): SortListAction {
    return sortCollection(LIST_SORT, field, direction) 
}

The compiler complains that the type 'string' is not assignable to '"LIST_SORT"'. What would be the right way to express the above?

Should I be using generics or casting the return value or is there a more straight-forward way to accomplish this?

1
  • I think I love the fact that there are at least three ways to do what you're trying to do above, that you can pick between based on other factors. I either love it, or it bothers me. :-) Commented Aug 7, 2019 at 14:26

2 Answers 2

3

If you want to preserve the string literal type, your best approach would be to add a generic type parameter to SortCollectionAction and sortCollection.

export interface SortCollectionAction<T extends string> {
    type: T
    payload: {
        field: string
        direction: string
    }
}

export function sortCollection<T extends string>(type: T, field: string, direction: string): SortCollectionAction<T> {
    return {
        type: type,
        payload: {
            field: field,
            direction: direction
        }
    }
}

const LIST_SORT = 'LIST_SORT'

interface SortListAction extends SortCollectionAction<typeof LIST_SORT> {
}

function sortList(field: string, direction: string): SortListAction {
    return sortCollection(LIST_SORT, field, direction) 
}

Play

It really depends what you plan to do later with type if this added complexity is worth it.

Sign up to request clarification or add additional context in comments.

Comments

2

If the type field is going to have any number of values, it should be of type string. You can make LIST_SORT a string like this:

const LIST_SORT: string = 'LIST_SORT';

On the playground.

If you're going to constrain type so that it can only be one of a set of values, you probably want a string enum instead:

enum ListType {
    LIST_SORT = 'LIST_SORT',
    // ...
}

Then you'd use ListType for the type field.

On the playground.

8 Comments

I was going to suggest sprinkling some generic type parameters to preserver the string literal type. This also works, really depends how important preserving it is for the use case..
@TitianCernicova-Dragomir - Cool, would be keen to see an answer with your take!
Thanks for the quick response! That really helps. I'm confused as to why const LIST_SORT = 'LIST_SORT'; doesn't automatically cast that as a string. E.g. why the explicit ': string' is necessary if this is a string assigned to a constant.
@T.J.Crowder - Makes sense. Thanks again!
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.