piotrwitek / typesafe-actions

Typesafe utilities for "action-creators" in Redux / Flux Architecture
https://codesandbox.io/s/github/piotrwitek/typesafe-actions/tree/master/codesandbox
MIT License
2.41k stars 98 forks source link

Did you consider pushing this further towards more boilerplate-saving? #12

Closed flq closed 6 years ago

flq commented 6 years ago

I was playing a bit with providing additional createAction methods which have a very specific shape and came up with the following for e.g. actions with a payload:

const createWithPayload = <P, T extends string>(typeString: T): ((payload: P) => PayloadAction<T, P> & TypeGetter<T>) => {
  const f = createAction(typeString, (payload: P) => ({ type: typeString, payload }));
  return f;
};

which you could call like this:

const a = createWithPayload<string, "CHANGE_COMMENT">("CHANGE_COMMENT");

You can push this even further:

const createWithPayload2 = <T extends string>(
  typeString: T
): (<P>() => ((payload: P) => PayloadAction<T, P> & TypeGetter<T>)) => {
  return <P>() => {
    const f = createAction(typeString, (payload: P) => ({ type: typeString, payload }));
    return f;
  };
};

And you call it like this:

const a = createWithPayload2("CHANGE_COMMENT")<string>();

// instead of:

const a = createAction("CHANGE_COMMENT", (payload: string) => ({
  type: "CHANGE_COMMENT",
  payload
}));

What do you think?

piotrwitek commented 6 years ago

Hello, Thanks for suggestion but you can easily read in motivation that this solution is about to be free from explicit type annotations. And moreover there is already at least one solution that I saw with same exact API that you propose.

Also there are more limitations to your proposal that are impossible to work around that I'm concerned about, for instance one of them is optional parameters. As an experiment please try to implement below example with your proposal and check if you can achieve the same resulting type as in the example :

const notify3 = createAction('NOTIFY',
    (username: string, message?: string) => ({
      type: 'NOTIFY',
      payload: { message: `${username}: ${message || ''}` },
      meta: { username, message },
    }),
  )
// resulting type:
// const notify3: (username: string, message?: string | undefined) => {
//   type: "NOTIFY";
//   payload: { message: string; };
//   meta: { username: string; message: string | undefined; };
// }

// you can call it without a second param!
notify3('Piotr')
flq commented 6 years ago

You're probably right that it makes more sense to keep those functions inside your domain, where you may have certain opinions about actions with a similar structure.

piotrwitek commented 6 years ago

@flq as to the boilerplate what I think should be possible is to remove the first typeString argument, so that then it become a simple decorator function and will be used like this:

const notify3 = createAction((username: string, message?: string) => ({
  type: 'NOTIFY',
  payload: { message: `${username}: ${message || ''}` },
  meta: { username, message },
}))

But probably in the next major release as the last time I tested there was some issue with type inference.