esamattis / immer-reducer

Type-safe and terse reducers with Typescript for React Hooks and Redux
http://npm.im/immer-reducer
MIT License
224 stars 15 forks source link

[Suggestion] Integration with redux-observable/redux-saga #3

Closed Alexxzz closed 5 years ago

Alexxzz commented 5 years ago

Hello,

Awesome library! Probably the most type safe and boilerplate free way of working with redux =)

To make integration with redux-observable/redux-saga possible we need some how to get the generated action type. This could be achieved by adding type property to an action creator function.

// to add a `type` parameter to action creator function
type ActionCreatorWithType<T> = {
  readonly type: T;
}

export type ActionCreators<ClassActions extends ImmerReducerClass> = {
    [K in keyof Methods<InstanceType<ClassActions>>]: (
        ...args: ArgumentsType<InstanceType<ClassActions>[K]>
    ) => {
        type: K;
        payload: ArgumentsType<InstanceType<ClassActions>[K]>;
    } & ActionCreatorWithType<K> // <<<< now action creator functions will have `type` param (not sure what is the better way to specify it here)
};

// ...

export function createActionCreators<T extends ImmerReducerClass>(
    immerReducerClass: T,
): ActionCreators<T> {
    const actionCreators: {[key: string]: Function} = {};

    Object.getOwnPropertyNames(immerReducerClass.prototype).forEach(key => {
        // ...    
        const type = `${PREFIX}:${getReducerName(immerReducerClass)}#${key}`;
        const actionCreator = (...args: any[]) => {
            return {
                type,
                payload: args,
            };
         };

        actionCreator.type = type; // <<<< set type to an action creator function 

        actionCreators[key] = actionCreator;
    });

    return actionCreators as any;
}

Now in redux-observable Epic:

type ActionType = ReturnType<typeof actionCreators.setVal>; 

const epic: Epic<ActionType> = (action$) =>
  action$.ofType(actionCreators.setVal.type) // <<<< getting type of an action to subscribe to it!
  .pipe(
    tap((a) => {
      // a.payload - will be typed
      // ...
    })
  );

If you think this is something useful I could work on a PR.

esamattis commented 5 years ago

Yes. Definitely interested in better redux-observable and redux-saga integration possibilities.

Your proposed implementation looks good on surface at least (I didn't actually try it yet). I'd be very glad to see proper PR of that.

If a type property is to be added to action creators you could probably get by without the ReturnType helper:

const epic: Epic<typeof actionCreators.setVal.type> = (action$) =>
  action$.ofType(actionCreators.setVal.type) // <<<< getting type of an action to subscribe to it!
  .pipe(
    tap((a) => {
      // a.payload - will be typed
      // ...
    })
  );
esamattis commented 5 years ago

Implemented in https://github.com/epeli/immer-reducer/pull/4