aikoven / typescript-fsa

Type-safe action creator utilities
MIT License
607 stars 28 forks source link

Optional `payload` in `ActionCreator`? #45

Closed suevalov closed 6 years ago

suevalov commented 7 years ago

Hello, thanks for the library!

I've got a question regarding async action creator. What if my action should receive no params?

export const FETCH_TEAMS = 'FETCH_TEAMS';
const fetchTeams = actionCreator.async<
    {}, // <- here, how can we specify to be optional?
    Array<Team>,
    AxiosError
>(FETCH_TEAMS);

// how i can handle it how
fetchTeams.started({});
fetchTeams.done({
  result: teams,
  params: {}
});

// how it would be nice to handle, but now i see errors in this case
fetchTeams.started();
fetchTeams.done({
  result: teams
});

Thanks in advance!

aikoven commented 7 years ago

Unfortunately, I don't know of a way to make it possible. We added support for it here but had to revert it due to a way TS resolves overloaded signature, which broke async action creators with payload.

If you find a solution that works, please let me know.

Meanwhile, I recommend you to make a wrapper that wraps functions returning promises and dispatches async actions automatically. See typescript-fsa-redux-thunk, typescript-fsa-redux-saga.

unional commented 7 years ago

Related discussion: https://github.com/Microsoft/TypeScript/issues/14400

rclmenezes commented 6 years ago

Similarly, empty actions return undefined payloads:

import actionCreatorFactory from 'typescript-fsa';
const actionCreator = actionCreatorFactory();

const action = actionCreator('ACTION');
console.log(action())  // {type: 'ACTION', payload: undefined}

It's possible for us to fix both by adopting the following syntax:

const emptyAction = actionCreator.empty('ACTION');
console.log(emptyAction())  // {type: 'ACTION'}

type Success = string;
type Error = string;
const asyncAction = actionCreator.emptyAsync<Success, Error>('ACTION');
console.log(asyncAction.started())  // {type: 'ACTION_STARTED'}
console.log(asyncAction.done('success'))  // {type: 'ACTION_DONE', payload: {results: 'success'}}
console.log(asyncAction.done('failure'))  // {type: 'ACTION_FAILED', payload: {results: 'failure'}}

Benefits of doing it this way:

Cons:

If you decide that you like this, I'd be happy to make the PR :)

aikoven commented 6 years ago

I would rather not expand the API because of TS shortcomings.

The undefined payload can be easily fixed at runtime, not the typing side.

I'm thinking about just making a payload parameter optional This would make it a bit less type-safe, but at least it would get easier to express things. For instance, the built-in typings for Promise do the same:

// in PromiseConstructor interface

new <T>(executor: (
  resolve: (value?: T | PromiseLike<T>) => void, 
  reject: (reason?: any) => void
) => void): Promise<T>;
aikoven commented 6 years ago

Fixed in 3.0.0-beta-1.

mc-petry commented 6 years ago

You can look how it's done in redux-handler. See .action() and .action<T>()

suevalov commented 6 years ago

Thanks everyone 👍