Giter VIP home page Giter VIP logo

typescript-fsa's Introduction

TypeScript FSA npm version Build Status

Action Creator library for TypeScript. Its goal is to provide type-safe experience with Flux actions with minimum boilerplate. Created actions are FSA-compliant:

interface Action<Payload> {
  type: string;
  payload: Payload;
  error?: boolean;
  meta?: Object;
}

Installation

npm install --save typescript-fsa

Usage

Basic

import actionCreatorFactory from 'typescript-fsa';

const actionCreator = actionCreatorFactory();

// Specify payload shape as generic type argument.
const somethingHappened = actionCreator<{foo: string}>('SOMETHING_HAPPENED');

// Get action creator type.
console.log(somethingHappened.type);  // SOMETHING_HAPPENED

// Create action.
const action = somethingHappened({foo: 'bar'});
console.log(action);  // {type: 'SOMETHING_HAPPENED', payload: {foo: 'bar'}}

Async Action Creators

Async Action Creators are objects with properties started, done and failed whose values are action creators. There is a number of Companion Packages that help with binding Async Action Creators to async processes.

import actionCreatorFactory from 'typescript-fsa';

const actionCreator = actionCreatorFactory();

// specify parameters and result shapes as generic type arguments
const doSomething =
  actionCreator.async<{foo: string},   // parameter type
                      {bar: number},   // success type
                      {code: number}   // error type
                     >('DO_SOMETHING');

console.log(doSomething.started({foo: 'lol'}));
// {type: 'DO_SOMETHING_STARTED', payload: {foo: 'lol'}}

console.log(doSomething.done({
  params: {foo: 'lol'},
  result: {bar: 42},
}));
// {type: 'DO_SOMETHING_DONE', payload: {
//   params: {foo: 'lol'},
//   result: {bar: 42},
// }}

console.log(doSomething.failed({
  params: {foo: 'lol'},
  error: {code: 42},
}));
// {type: 'DO_SOMETHING_FAILED', payload: {
//   params: {foo: 'lol'},
//   error: {code: 42},
// }, error: true}

Actions With Type Prefix

You can specify a prefix that will be prepended to all action types. This is useful to namespace library actions as well as for large projects where it's convenient to keep actions near the component that dispatches them.

// MyComponent.actions.ts
import actionCreatorFactory from 'typescript-fsa';

const actionCreator = actionCreatorFactory('MyComponent');

const somethingHappened = actionCreator<{foo: string}>('SOMETHING_HAPPENED');

const action = somethingHappened({foo: 'bar'});
console.log(action);
// {type: 'MyComponent/SOMETHING_HAPPENED', payload: {foo: 'bar'}}

Redux

// actions.ts
import actionCreatorFactory from 'typescript-fsa';

const actionCreator = actionCreatorFactory();

export const somethingHappened =
  actionCreator<{foo: string}>('SOMETHING_HAPPENED');
export const somethingAsync =
  actionCreator.async<{foo: string},
                      {bar: string}
                     >('SOMETHING_ASYNC');


// reducer.ts
import {Action} from 'redux';
import {isType} from 'typescript-fsa';
import {somethingHappened, somethingAsync} from './actions';

type State = {bar: string};

export const reducer = (state: State, action: Action): State => {
  if (isType(action, somethingHappened)) {
    // action.payload is inferred as {foo: string};

    action.payload.bar;  // error

    return {bar: action.payload.foo};
  }

  if (isType(action, somethingAsync.started)) {
    return {bar: action.payload.foo};
  }

  if (isType(action, somethingAsync.done)) {
    return {bar: action.payload.result.bar};
  }

  return state;
};

redux-observable

// epic.ts
import {Action} from 'redux';
import {Observable} from 'rxjs';
import {somethingAsync} from './actions';

export const epic = (actions$: Observable<Action>) =>
  actions$.filter(somethingAsync.started.match)
    .delay(2000)
    .map(action => {
      // action.payload is inferred as {foo: string};

      action.payload.bar;  // error

      return somethingAsync.done({
        params: action.payload,
        result: {
          bar: 'bar',
        },
      });
    });

Companion Packages

Resources

API

actionCreatorFactory(prefix?: string, defaultIsError?: Predicate): ActionCreatorFactory

Creates Action Creator factory with optional prefix for action types.

  • prefix?: string: Prefix to be prepended to action types as <prefix>/<type>.
  • defaultIsError?: Predicate: Function that detects whether action is error given the payload. Default is payload => payload instanceof Error.

ActionCreatorFactory<Payload>#(type: string, commonMeta?: object, isError?: boolean): ActionCreator<Payload>

Creates Action Creator that produces actions with given type and payload of type Payload.

  • type: string: Type of created actions.
  • commonMeta?: object: Metadata added to created actions.
  • isError?: boolean: Defines whether created actions are error actions.

ActionCreatorFactory#async<Params, Result, Error>(type: string, commonMeta?: object): AsyncActionCreators<Params, Result, Error>

Creates three Action Creators:

  • started: ActionCreator<Params>
  • done: ActionCreator<{params: Params, result: Result}>
  • failed: ActionCreator<{params: Params, error: Error}>

Useful to wrap asynchronous processes.

  • type: string: Prefix for types of created actions, which will have types ${type}_STARTED, ${type}_DONE and ${type}_FAILED.
  • commonMeta?: object: Metadata added to created actions.

ActionCreator<Payload>#(payload: Payload, meta?: object): Action<Payload>

Creates action with given payload and metadata.

  • payload: Payload: Action payload.
  • meta?: object: Action metadata. Merged with commonMeta of Action Creator.

isType(action: Action, actionCreator: ActionCreator): boolean

Returns true if action has the same type as action creator. Defines Type Guard that lets TypeScript know payload type inside blocks where isType returned true:

const somethingHappened = actionCreator<{foo: string}>('SOMETHING_HAPPENED');

if (isType(action, somethingHappened)) {
  // action.payload has type {foo: string}
}

ActionCreator#match(action: Action): boolean

Identical to isType except it is exposed as a bound method of an action creator. Since it is bound and takes a single argument it is ideal for passing to a filtering function like Array.prototype.filter or RxJS's Observable.prototype.filter.

const somethingHappened = actionCreator<{foo: string}>('SOMETHING_HAPPENED');
const somethingElseHappened =
  actionCreator<{bar: number}>('SOMETHING_ELSE_HAPPENED');

if (somethingHappened.match(action)) {
  // action.payload has type {foo: string}
}

const actionArray = [
  somethingHappened({foo: 'foo'}),
  somethingElseHappened({bar: 5}),
];

// somethingHappenedArray has inferred type Action<{foo: string}>[]
const somethingHappenedArray = actionArray.filter(somethingHappened.match);

For more on using Array.prototype.filter as a type guard, see this github issue.

typescript-fsa's People

Contributors

aikoven avatar alexanderotavka avatar christineoo avatar cristicbz avatar dphilipson avatar greenkeeper[bot] avatar joeferraro avatar m0a avatar ssynix avatar suutari-ai avatar ttamminen avatar uzimith avatar zalastax avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

typescript-fsa's Issues

Changing interface to type for ActionCreator<P> in 3.0-beta-2

I'm currently using typescript-fsa version 2.5. I'm augmenting ActionCreator interface like this:

declare module "typescript-fsa" {
  export interface ActionCreator<P> {
    type: string;
    payloadType: P;
    match: (action: Fsa.AnyAction) => action is Action<P>;
    (payload: P, meta?: Meta): Action<P>;
  }
}

The reason I'm doing it is that I extended it with payloadType. In 3.0 I'm not able to do that since types in typescript cannot be augmented. Is there any reason why you decided to use type?

Use case for isType looser type on actionCreator parameter maybe ?

Love your work, so much nicer to have good types.
I've been using it with the typescript-fsa-redux-thunk wrapper bindActionThunk().

I have a use case at the moment where I am creating myIsType based on isType.
The function prototype on the actionCreator parameter gives type errors.
I was wondering if the type for that parameter on isType might be loosened without risk ?

pendingAsyncActions is just an array of all the action async api creators that I care to keep track of pending activity in the application.

I still feel a bit of depth with complexities of typscripts types and interpretting its error messages I tried to create a more restrictive type for myIsType than any but quickly got complex type messages that defeated me.

const myIsType = isType as any;

export default function pendingCountReducer(
  count: State = InitialState,
  action: AnyAction
) {
  pendingAsyncActions.forEach(apiAction => {
    if (isType(action, apiAction.started)) {
      count = count + 1;
    } else if (
      isType(action, apiAction.failed) ||
      myIsType(action, apiAction.done)
    ) {
      count = count - 1;
    }
  });
  return count;
}

Version 10 of node.js has been released

Version 10 of Node.js (code name Dubnium) has been released! 🎊

To see what happens to your code in Node.js 10, Greenkeeper has created a branch with the following changes:

  • Added the new Node.js version to your .travis.yml

If you’re interested in upgrading this repo to Node.js 10, you can open a PR with these changes. Please note that this issue is just intended as a friendly reminder and the PR as a possible starting point for getting your code running on Node.js 10.

More information on this issue

Greenkeeper has checked the engines key in any package.json file, the .nvmrc file, and the .travis.yml file, if present.

  • engines was only updated if it defined a single version, not a range.
  • .nvmrc was updated to Node.js 10
  • .travis.yml was only changed if there was a root-level node_js that didn’t already include Node.js 10, such as node or lts/*. In this case, the new version was appended to the list. We didn’t touch job or matrix configurations because these tend to be quite specific and complex, and it’s difficult to infer what the intentions were.

For many simpler .travis.yml configurations, this PR should suffice as-is, but depending on what you’re doing it may require additional work or may not be applicable at all. We’re also aware that you may have good reasons to not update to Node.js 10, which is why this was sent as an issue and not a pull request. Feel free to delete it without comment, I’m a humble robot and won’t feel rejected πŸ€–


FAQ and help

There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


Your Greenkeeper Bot 🌴

Consider typescript-fsa-reducers for Companion Packages section

Hello, I'm a big fan of this library. Thank you for creating it!

I've written a package, typescript-fsa-reducers, which builds on top of typescript-fsa to simplify writing reducers. I wanted to bring it to your attention in case you're interested in adding it to the "Companion Packages" section of your README. If not, no hard feelings, and thanks again for the work you've done.

How to use isType with async properties

Hi,
I have the following:

interface ILanguage {
  first: string
  second?: string
}
const actionCreator = actionCreatorFactory( '[Language]' )
export const addLangAction = actionCreator.async<
    ILanguage, { success: number }, { error: number }>( 'ADD_DATA' )

export function langReducer( state: ILanguage, action: Action ) {
  if ( isType( action, addLangAction) ) {
    return action.payload
  }
  return state
}

The addLangAction generates the following error:

ERROR in language.store.ts (90,24): Argument of type 'AsyncAction
Creators<ILanguage, { success: number; }, { error: number; }>' is not assignable to parameter of type 'ActionCreator<{}>'.
  Type 'AsyncActionCreators<ILanguage, { success: number; }, { error: number; }>' provides no match for the signature '(payload
: {}, meta?: Object): Action<{}>')

Add separate project for async-thunk implementation

@aikoven provided a solution for redux-thunk integration and I think this should be a separate package with the util + documentation. I use redux-thunk, so I can contribute to the usage documentation.

I have modified it slightly for readability and added TError generic.

import { AsyncActionCreators } from 'typescript-fsa'

// https://github.com/aikoven/typescript-fsa/issues/5#issuecomment-255347353
function wrapAsyncWorker<TParameters, TSuccess, TError>(
  asyncAction: AsyncActionCreators<TParameters, TSuccess, TError>,
  worker: (params: TParameters) => Promise<TSuccess>,
) {
  return function wrappedWorker(dispatch, params: TParameters): Promise<TSuccess> {
    dispatch(asyncAction.started(params));
    return worker(params).then(result => {
      dispatch(asyncAction.done({ params, result }));
      return result;
    }, (error: TError) => {
      dispatch(asyncAction.failed({ params, error }));
      throw error;
    });
  }
}

export default wrapAsyncWorker

2.1.0 breaking change; async start() with payload is now forbidden.

So... I was using a real payload in async .start(), and now it won't even compile, which I suppose is a good thing, since it doesn't work. Clearly this is intentional; there's even a test to make sure payloads aren't allowed.

Semver, please? If you're going to deliberately break stuff that works now, bump the major rev number.

Falling back to 2.0 for now. Please let me know if this (i.e., forbidding .start() payload) is permanent.

Change definition of meta?

When I try to set a field on meta, TypeScript complains that the property does not exist.

For example:

myAction.meta.requestId = 123

>> Property 'requestId' does not exist on type 'Object'.

The current definition of meta in Action<P> looks like this:

  meta?: Object | null;

If I change it as follows, then I can place any fields into meta with no errors.

  meta?: { [x: string]: any } | null;

Of course this means meta is not type-safe, but it's no worse than the current definition.

Another alternative would be to provide a type parameter for meta (e.g., Action<P,M>), but that might be a bit of overkill for a field like meta.

Any interest in either of these changes?

Might be useful to have examples folder

It would be nice to have an examples folder with a real redux app with all the various ways this can be used and best practices, Esp async stuff.

My use case is that I'm trying to understand a codebase I've inherited and am not sure if this is a standard practice or not:

import { Site } from '../../model/Site';
import { loadSites as eventApiLoadSites } from '../../rest-api/Event-api';
import { Dispatch } from 'redux';
import { actionCreatorFactory, AnyAction } from 'typescript-fsa';
import wrapAsyncWorker from './wrapAsyncWorker';

const actionCreator = actionCreatorFactory();

export const LoadSites = actionCreator.async<null, Site[]>('LoadSites');

const loadSitesWorker =
  wrapAsyncWorker(LoadSites, (): Promise<Site[]> => eventApiLoadSites());

export const loadSites = () => {
  return (dispatch: Dispatch<AnyAction>) => loadSitesWorker(dispatch, null);
};

loadSites() is passed to an argument to store.dispatch and is giving me typing errors:

TypeScript error: Argument of type '(dispatch: Dispatch<AnyAction>) => Promise<Site[]>' is not assignable to parameter of type 'AnyAction'.

Optional parameters not respected in 3.0.0

When I upgrade from 3.0.0-beta-2 to 3.0.0 I'm getting loads of

error TS2554: Expected 1-2 arguments but got 0.

for all my actionFactory<void> & actionFactory.async<void, SomeType>.

Reverting back to 3.0.0-beta-2 solves it. I'm on TypeScript 3.1.6.

Rename package

I think it might be good to rename this package to typescript-fsa or fsa-typescript or something along those lines. This library can technically be used with any FSA compliant flux implementation, or even something else. For example I am currently using it in a cycle.js project.

On top of this, the redux dependency probably isn't necessary. We could reimplement the ReduxAction type in this library and be fairly certain that the API won't change as FSA is a pretty concrete interface at this point in time.

Also if you haven't seen it, should take a look at https://github.com/UrbanDoor/typed-fsa. Seems there are similar goals for both of these projects. Though I prefer this library's isType check over that other library's isAction check for narrowing, as there is no need to declare the payload again.

Thanks for all the hard work!

Some breakage with new 3.0 beta

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?

adapting FSA's typing?

flux-standard-action has its own typing. Would this library able to consume and compatible with it?

The main difference is that it separates FSA and ErrorFSA, and FSA takes two generics FSA<Payload, Meta>.

🌷

AsyncAction sucess assumes payload.result

Trying to use this, but my async actions will return the result type as the payload, rather than under action.payload.result
When I use isType(action, someaction.done) payload is now typed to be ActionCreator<Success<P, S>> so according to typescript, I need to grab the actual api result from action.payload.result
I'm using middleware, so cant easily change this just for the files I am using this on. is there a way round this?

Success results are always optional now

Hi, I've just tried updating to TS 3.0.1 and ts-fsa 3.0.0-beta-2, i previously had this generic reducer...

const mergeResultIntoIndex = <T extends Identified>(state: Indexed<T>, {result}: Success<any, T>): Indexed<T> => {
    return {
        ...state,
        [result.id]: result
    }
}

where:

export interface Identified {
    readonly id: string
}

export type Indexed<V> = { [id: string]: V }

but now i'm seeing the error:

TS2418: Type of computed property's value is 'T | undefined', which is not assignable to type 'T'.

for the line:

        [result.id]: result

Feature request: Add optional mapPayload parameter

I would appreciate the ability to add an optional parameter for mapping the values of an action's payload. This would be useful, for example, for dispatching notifications and assigning them a UUID in the action itself.

My current work-around is the following wrapper function:

import { Action, AnyAction, Meta } from 'typescript-fsa'

// Copy-pasted from typescript-fsa, with Overload edits
type ActionCreator<Payload, Overload = Payload> = {
  type: string
  match: (action: AnyAction) => action is Action<Overload>
  (payload: Payload, meta?: Meta): Action<Overload>
} & (Payload extends void
  ? (payload?: Payload, meta?: Meta) => Action<Overload>
  : {})

function mapActionCreatorPayload<P extends {}, O extends {} = P>(
  actionCreator: ActionCreator<P>,
  mapPayload: (payload: P) => O
) {
  const action = (payload: P): Action<ReturnType<typeof mapPayload>> => ({
    type: actionCreator.type,
    payload: mapPayload(payload)
  })

  const overload = Object.assign(action, {
    type: actionCreator.type,
    match: actionCreator.match
  })

  // Type 'Payload' is not comparable to type 'Overload'
  return (overload as unknown) as ActionCreator<
    P,
    ReturnType<typeof mapPayload>
  >
}

The usage is then:

const actionCreator = actionCreatorFactory('notifications')
const addAction = actionCreator<NotificationWithoutId>('ADD')

export const addNotification = mapActionCreatorPayload(
    addAction,
    (notification): NotificationWithId => ({
      ...notification,
      id: uuid()
    })
  )

This works perfectly in the sense that addNotification doesn't accept and id key in its argument payload (NotificationWithoutId), but the resulting payload in Redux will contain the id (NotificationWithId).

I think this would be an useful optional feature right in TypeScript-FSA.

Typings for dispatch props

Hi,
I'm trying to provide typings for my actions inside a connected component.

interface IDispatchProps {
  setName: typeof setName // where setName is an imported action
}

Given that the action is defined as:

export const setName = actionCreator<string>('SET_NAME');

The idea is to use those typings when defining mapDispatchToProps:

const mapDispatchToProps = (dispatch): IDispatchProps => ({
  setName: (name:string) =>dispatch(setName(name))
})

However I get an error about the typings being incompatible. Is there a way to get the type of that action?

Amazing project, thank you! πŸŽ‰

Hello there πŸ‘‹,

I would like to thank your for this project and your time invested in this project,

I just rewrote a project in TS and I was handling everything with Enums and custom utilities,

Looking for good patterns, I landed in this repo, all the *-fsa-* packages are awesome!

The code looks more tidy and organized!

Many thanks πŸ™

match incompatible for TypeScript 3.3.4 later

error info:

(property) AsyncActionCreators<string, void, Error>.done: {
    (payload: Success<string, void>, meta?: {
        [key: string]: any;
    } | null | undefined): Action<Success<string, void>>;
    type: string;
    match: (action: AnyAction) => action is Action<Success<string, void>>;
}

Argument of type '{ (payload: Success<string, void>, meta?: { [key: string]: any; } | null | undefined): Action<Success<string, void>>; type: string; match: (action: AnyAction) => action is Action<Success<string, void>>; }' is not assignable to parameter of type '{ (payload: { params: string; } & { result: void; }, meta?: { [key: string]: any; } | null | undefined): Action<{ params: string; } & { result: void; }>; type: string; match: (action: AnyAction) => action is Action<{ ...; } & { ...; }>; }'.
  Types of property 'match' are incompatible.
    Type '(action: AnyAction) => action is Action<Success<string, void>>' is not assignable to type '(action: AnyAction) => action is Action<{ params: string; } & { result: void; }>'.
      Type predicate 'action is Action<Success<string, void>>' is not assignable to 'action is Action<{ params: string; } & { result: void; }>'.
        Type 'Action<Success<string, void>>' is not assignable to type 'Action<{ params: string; } & { result: void; }>'.
          Type 'Success<string, void>' is not assignable to type '{ params: string; } & { result: void; }'.
            Type '{ params: string; } & { result?: void | undefined; }' is not assignable to type '{ params: string; } & { result: void; }'.
              Type '{ params: string; } & { result?: void | undefined; }' is not assignable to type '{ result: void; }'.
                Property 'result' is optional in type '{ params: string; } & { result?: void | undefined; }' but required in type '{ result: void; }'.

ts(2345)

errata code:

export type Success<Params, Result> =
({params: Params} | (Params extends void ? {params?: Params} : never)) &
({result: Result} | (Result extends void ? {result?: Result} : never));
export type Failure<Params, Error> =
({params: Params} | (Params extends void ? {params?: Params} : never)) &
{error: Error};

if params is void returns never?

if result is void returns never?

diff --git a/src/index.ts b/src/index.ts
index 5e99796..e1dc9dc 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -82,11 +82,11 @@ export type ActionCreator<Payload> = {
 );

 export type Success<Params, Result> =
-  ({params: Params} | (Params extends void ? {params?: Params} : never)) &
-  ({result: Result} | (Result extends void ? {result?: Result} : never));
+  ({params: Params} | (Params extends void ? never : {params?: Params})) &
+  ({result: Result} | (Result extends void ? never : {result?: Result}));

 export type Failure<Params, Error> =
-  ({params: Params} | (Params extends void ? {params?: Params} : never)) &
+  ({params: Params} | (Params extends void ? never : {params?: Params})) &
   {error: Error};

 export interface AsyncActionCreators<Params, Result, Error = {}> {

TypeScript 3.6 Compatibility issue

Example code

fsa-issue.zip

$ unzip fsa-issue.zip
$ cd fsa-issue
$ npm install
$ npm test

> fsa-issue@ test /Users/septs/Projects/fsa-issue
> tsc --noEmit

issue.ts:8:6 - error TS2345: Argument of type '{ (payload: Success<unknown, void>, meta?: { [key: string]: any; } | null | undefined): Action<Success<unknown, void>>; type: string; match: (action: AnyAction) => action is Action<Success<unknown, void>>; }' is not assignable to parameter of type 'ActionCreator<void>'.
  Type '{ (payload: Success<unknown, void>, meta?: { [key: string]: any; } | null | undefined): Action<Success<unknown, void>>; type: string; match: (action: AnyAction) => action is Action<Success<unknown, void>>; }' is not assignable to type '{ (payload: void, meta?: { [key: string]: any; } | null | undefined): Action<void>; type: string; match: (action: AnyAction) => action is Action<void>; }'.
    Types of property 'match' are incompatible.
      Type '(action: AnyAction) => action is Action<Success<unknown, void>>' is not assignable to type '(action: AnyAction) => action is Action<void>'.
        Type predicate 'action is Action<Success<unknown, void>>' is not assignable to 'action is Action<void>'.
          Type 'Action<Success<unknown, void>>' is not assignable to type 'Action<void>'.
            Type 'Success<unknown, void>' is not assignable to type 'void'.
              Type '{ params: unknown; } & { result: void; }' is not assignable to type 'void'.

8 test(async.done)

npm ERR! Test failed.  See above for more details.

Why void with Result?

typescript-fsa-redux-thunk in NO RETURN async procedure

const doThing = createAsyncAction("DO_THING", async () => { return; });

// doThing.async.done
// The result of the reasoning type is:
//      Action<Success<unknown, void>>

see xdave/typescript-fsa-redux-thunk#17
see xdave/typescript-fsa-redux-thunk#14

An in-range update of ts-node is breaking the build 🚨

Version 3.0.5 of ts-node just got published.

Branch Build failing 🚨
Dependency ts-node
Current Version 3.0.4
Type devDependency

This version is covered by your current version range and after updating it in your project the build failed.

As ts-node is β€œonly” a devDependency of this project it might not break production or downstream projects, but β€œonly” your build or test tools – preventing new deploys or publishes.

I recommend you give this issue a high priority. I’m sure you can resolve this πŸ’ͺ

Status Details
  • ❌ continuous-integration/travis-ci/push The Travis CI build could not complete due to an error Details

Commits

The new version differs by 7 commits.

  • c18331a 3.0.5
  • 5cf97b1 Add --inspect-brk to known flags (#343)
  • 7dfb3e3 Pin node tests at 6.x (#340)
  • 633d537 chore(package): update chai to version 4.0.1 (#337)
  • b751a56 Define exports and improve getTypeInfo help (#332)
  • d018300 Update yn default option
  • cc3bf22 Expose _ bin file for consumers to use

See the full diff

Not sure how things should work exactly?

There is a collection of frequently asked questions and of course you may always ask my humans.


Your Greenkeeper Bot 🌴

Bug: can't create an action without payload. 'ActionCreator<void>' is not assignable to '() => void'

Here's a rough example:

// actions.ts
import actionCreatorFactory from 'typescript-fsa';
const actionCreator = actionCreatorFactory('tasks');
export const allDone = actionCreator('MARK_ALL_DONE');
export const markDone = actionCreator<number>('MARK_DONE');

// Tasks.tsx
interface ITasksProps = {
  allDone: () => void;
  markDone: (taskId: number) => void;
}
...

// TasksContainer.tsx
import Tasks from './Tasks'
import { allDone, markDone } from './actions';
...
const mapDispatchToProps = {
  allDone,
  markDone,
};

connect(null, mapDispatchToProps)(Tasks);

Here's an error:

Type 'ActionCreator<void>' is not assignable to type '() => void'.  TS2345

To fix it, I have to change this:

  allDone: () => void;

To this:

  allDone: (payload: void) => void;

Which is stupid.

payload with default values?

how to handle default params in async action creator?

const asyncAction =ac.async<Params | underfined, Result, Error>('async')

// [ts] Expected 1-2 arguments, but got 0.
asyncAction.started()

I want started() method parameter be optional. I try to make it Params | void but it doesn't work as expect.

The current workaround is to modify the index.d.ts file

export declare type ActionCreator<Payload> = {

  // add the following declaration
  (payload?: Payload, meta?: Meta): Action<Payload>;

}

Is there a better way to accomplish that?

Customizing the returned payload

How could I do something like the following in typescript-fsa?

interface AddTodoInputPayload {
   name: string
}

interface AddTodoPayload extends AddTodoInputPayload {
   type: 'ADD_TODO',
   id: string
}
const addTodoAction = (payload: AddTodoInputPayload): AddTodoPayload => ({...payload, type: 'ADD_TODO',  id: uuid.v4()})

I know the type is added automatically and all, but I want to be able to augment/modify the passed payload, is this possible? I'd also like to preserve composability with typescript-fsa-reducers.

Duplicate identifier 'Action'

Hi,

I just copy and pasted your example but can't seem to get around the duplicate identifier I'm seeing;

// reducer.ts
import {Action} from 'redux';
import {isType, Action} from 'redux-typescript-actions';

Am I doing something wrong?

Typing issues with react-redux connect and class components

Not sure if this is related to #51 or #32, but I have issues with typescript-fsa in connection to react-redux' connect and class components.

In the following example, the class component version does not compile, but the functional component version does:

import * as React from 'react';
import actionCreatorFactory, { ActionCreator } from 'typescript-fsa';
import { connect } from 'react-redux';

interface Props {
  doSomething: (thing: string) => void;
}
class MyComponent extends React.Component<Props> {
  render() {
    return <button onClick={() => this.props.doSomething('test')}/>;
  }
}
const MyFunctionalComponent = (props: Props) => <button onClick={() => props.doSomething('test')}/>;

const createActionCreator = actionCreatorFactory();
const doSomething = createActionCreator<string>('DO_SOMETHING');

const mapDispatchToProps = {
  doSomething,
};
connect(null, mapDispatchToProps)(MyComponent); // error: Argument not assignable
connect(null, mapDispatchToProps)(MyFunctionalComponent); // works

Both work if I declare Props as

interface Props {
  doSomething: ActionCreator<string>;
}

But I obviously do not want to couple the component to redux-specific stuff. Using typescript 2.8 and the most up-to-date version of all packages. Any ideas?

Optional `payload` in `ActionCreator`?

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!

Allow For Passing Meta Type ActionCreatorFactory.async and AsyncActionCreators

Hi there-

Issue: I am unable to pass a TypeScript Type for the meta object that an actionCreator produces.


Example:
I want to create async actions for an event "login":

interface LogInData { email: string }
interface LogInResponseData { token: string }
interface LogInErrorData { error: string }

const logIn = actionCreator.async<LogInData, LogInResponseData, LogInErrorData>('LOGIN');

I also want to pass data via the meta object on the action.

dispatch(login.started({email: '[email protected]'}, {info: true}))

I now want to define what my action will look like, as well as create the saga:

import {Action} from 'redux';

interface LoginAction extends Action {
  meta: { info: boolean }
}

export function* loginFunc(action: LoginAction) {
  ...irrelevant
}

takeEvery(logIn.started,loginFunc),

The issue arises when you try to test the code:

const startAction = logIn.start({email: '[email protected]'}, {info: true})

loginFunc(startAction)

startAction will throw a TypeScript error because its meta property has a different definition than that of LoginAction


My suggestion would be to add something like:

const logIn = actionCreator.async<LogInData, LogInResponseData, LogInErrorData, MetaData>('LOGIN');

Action with unknown payload (TypeScript 3)

I've recently updated to TS3, and although [email protected] isn't currently working for me (reported in a separate issue), i'm continuing to use 2.5.0.

I have found that the new unknown type is incredibly useful as the payload type of an Action, especially in combination with redux-observable, and the ofAction operator (see m0a/typescript-fsa-redux-observable#6).

With this introduction of unknown, and some refactoring it's become clear to me that all of my use case of AnyAction should really be Action<unknown>, and I find I now have Action<unknown> appearing all over the place. This is making me think that unknown should be the default payload type, and there really is no place for AnyAction in well typed code.

I'm wondering whether it would be appropriate for Action type declaration to default its payload type param to unknown (obviously backwards compatibly would need to be accounted for).

This may just be due to my use-cases and usage of redux-observable Epics, and the narrowing effect of the ofAction from Action<unknown> to Action<P>. I'm not sure if it would be appropriate for users of thunk, saga, or other non-redux based uses.

Async actions should return the same type differentiated by an error object

From the flux-standard-action repo:

Flux actions can be thought of as an asynchronous sequence of values. It is important for asynchronous sequences to deal with errors. Currently, many Flux implementations don't do this, and instead define separate action types like LOAD_SUCCESS and LOAD_FAILURE. This is less than ideal, because it overloads two separate concerns: disambiguating actions of a certain type from the "global" action sequence, and indicating whether or not an action represents an error. FSA treats errors as a first class concept.

Was there a particular reasoning to do it with *_DONE/*_FAILED?

Consider removing the Node dependency

When running in the browser, process is undefined. A workaround is to define window.process = { env: { NODE_ENV: 'mycurrentenvironment' }}. It is conceivable that a common use case for typsecript-fsa is SPAs running in the browser, so this workaround should not be needed.

connect(, mapDispatchToProps) not working properly with async functions

This is probably a design problem with typescript-fsa and async actions. I'm using a pattern such as the following:

 const LoginActions = {
  syncAction: actionCreator<UserDetails>('USER_DETAILS_SET'),
  asyncAction: actionCreator.async<{phoneNumber: string}, {newAccountValid: boolean}>('CONFIRM_PHONE')
}

class LoginScreen extends Component<typeof LoginActions> {
  render() {
  }

  someButtonClicked = () => {
    this.props.syncAction({...}); // works properly
    this.props.asyncAction.started({...}); // undefined is not an object.. evaulating .started
  }
}

export default connect({}, LoginActions)(LoginScreen);

I understand why this happens, it's because the connect() call can see that syncAction is indeed a function and properly maps the dispatch, but asyncAction is an object with .started(), .done() functions defined within it, so it is ignored in the mapping by the connect() call.

Is there something simple I'm overlooking to get this pattern working with async actions?

Usage with redux connect

I was trying to do the following

import * as actions from '../actions/actions';
import {ActionCreatorsMapObject} from "redux";

export default connect(state => state, (dispatch) => {
  return bindActionCreators(actions, dispatch);
})(someComponent);

and I got the following error

 error TS2345: Argument of type 'typeof ".../actions/actions"' is not assignable to parameter of type 'ActionCreatorsMapObject'.

as a workaround i had to do

import * as actions from '../actions/actions';
import {ActionCreatorsMapObject, bindActionCreators} from "redux";

export default connect(state => state, (dispatch) => {
  return bindActionCreators(actions as any as ActionCreatorsMapObject, dispatch);
})(temp);

or alternatively

import * as actions from '../actions/actions';
import {ActionCreatorsMapObject, bindActionCreators} from "redux";

export default connect(state => state, (dispatch) => {
  return bindActionCreators({...actions}, dispatch);
})(temp);

I am not sure if it is a mismatch of type signatures or the typescript module system

Make ActionCreatorFactory.async return an additional 'reset' action creator

Hi,

Firstly thanks for the library, it's proved very useful for me.

I just wanted to provide a small suggestion and get your thoughts on it.

One of the useful functions in ActionCreatorFactory is:
async<P, S, E>(type: string, commonMeta?: Meta): AsyncActionCreators<P, S, E>;

Which creates started, done, failed action creators. Would it not also be useful if it was to create a reset action creator that allows us to handle a reset action for resetting the async operation state to the initial one. I can create one manually, but I thought it would be nicer if the current function did that alongside the other action creators.

A reset action creator can become useful in the following scenario. I have the following state for an API operation:

interface IAPIOperation {
  pending: boolean;
  success: boolean;
  failure: boolean;
}

When I receive started, I set IAPIOperation.pending to true, and when I receive done, I set IAPIOperation.success to true, etc.

In my React container I watch for IAPIOperation.success to be true, and when it is, I redirect to another page. The issue is if I visit the page for that container again, it will detect that IAPIOperation.success is still true, and keep redirecting me. A reset would prevent that. Unless there is a nicer way of preventing that without the need for a reset.

Thanks

Question about `done` and `failed` action creators

Is there a way to skip params in async action results? Something like:

    done: ActionCreator<S>;
    failed: ActionCreator<E>;

Let's say I want to fetch all users. This is kind of what I expected: (pseudocode)

dispatch fetchingAllUsers.started();
// do the fetching
if (error) {
    fetchingAllUsers.failed({ error: error.message });
} else {
    fetchingAllUsers.done({ users: users[] });
}

I'm mostly new to all this so I was wondering if I'm missing something or if there's a better way to utilize this library.

Thanks!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.