Skip to content

Commit 8e6014d

Browse files
Added support for effects
1 parent a0f2388 commit 8e6014d

14 files changed

+282
-38
lines changed

dist/main.js

+2-16
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

index.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@ import * as React from "react";
22
import * as ReactDOM from "react-dom";
33
import { HelloWorldContainer } from "./containers/hello-world/hello-world-container";
44

5-
ReactDOM.render(<HelloWorldContainer />, document.getElementById("app"));
5+
ReactDOM.render(<HelloWorldContainer anotherProp={"hi"} />, document.getElementById("app"));

lib/action.ts

+1-3
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@ export type ActionCreator<TActionType extends string = string, TPayload = null>
66
? (() => Action<TActionType>)
77
: ((payload: TPayload) => Action<TActionType, TPayload>);
88

9-
export type PayloadOfActionCreator<TActionCreator> = TActionCreator extends ActionCreator<infer TActionType, infer TPayload>
10-
? (TPayload extends {} ? null : TPayload)
11-
: never;
9+
export type PayloadOfActionCreator<TActionCreator> = TActionCreator extends ActionCreator<infer TActionType, infer TPayload> ? TPayload : never;
1210

1311
export type ActionTypeOfActionCreator<TActionCreator> = TActionCreator extends ActionCreator<infer TActionType, infer TPayload> ? TActionType : never;

lib/connect.tsx

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { Store, Dispatch } from "./store";
21
import * as React from "react";
2+
import { Store } from "./store";
33
import { Subscription } from "rxjs";
44
import { Reducers } from "./reducer";
55
import { ActionCreator } from "./action";
@@ -22,8 +22,8 @@ const mapSelectorProps = <TState, TSelectorProps extends SelectorProps<TState>,
2222
return stateProps;
2323
};
2424

25-
const mapActionProps = <TState, TReducers, TActionProps, TProps, TActionKeys extends keyof TProps>(
26-
store: Store<TState, TReducers>,
25+
const mapActionProps = <TState, TReducers, TActionProps, TProps, TActionKeys extends keyof TProps, TActions>(
26+
store: Store<TState, TReducers, TActions>,
2727
actionProps: TActionProps,
2828
) => {
2929
const dispatchProps = Object.keys(actionProps).reduce(
@@ -37,7 +37,7 @@ const mapActionProps = <TState, TReducers, TActionProps, TProps, TActionKeys ext
3737
return dispatchProps;
3838
};
3939

40-
export const connect = <TState, TReducers extends Reducers<TState>>(store: Store<TState, TReducers>) => <
40+
export const connect = <TState, TReducers extends Reducers<TState>, TActions>(store: Store<TState, TReducers, TActions>) => <
4141
TSelectorProps extends SelectorProps<TState>,
4242
TActionProps extends ActionProps
4343
>(
@@ -49,7 +49,7 @@ export const connect = <TState, TReducers extends Reducers<TState>>(store: Store
4949
>(
5050
Component: React.ComponentType<TProps>,
5151
): React.ComponentType<ExternalProps> => {
52-
const dispatchProps = mapActionProps<TState, TReducers, TActionProps, TProps, keyof TActionProps>(store, actionProps);
52+
const dispatchProps = mapActionProps<TState, TReducers, TActionProps, TProps, keyof TActionProps, TActions>(store, actionProps);
5353

5454
return class extends React.Component<ExternalProps, { subscription: Subscription; stateProps: Pick<TProps, keyof TSelectorProps> }> {
5555
public constructor(props: ExternalProps) {

lib/effect.ts

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { Observable, merge } from "rxjs";
2+
import { Store } from "./store";
3+
import { scan, startWith, filter } from "rxjs/operators";
4+
import { Action } from "./action";
5+
import { Reducers } from "./reducer";
6+
7+
export type Effect<TInputAction, TOutputAction, TState> = (action$: Observable<TInputAction>, state$: Observable<TState>) => Observable<TOutputAction>;
8+
9+
export const useEffect = <TInputAction extends Action<string, unknown>, TOutputAction extends TInputAction, TState>(
10+
effect: Effect<TInputAction, TOutputAction, TState>,
11+
) => <TReducers extends Reducers<TState, unknown>>(store: Store<TState, TReducers, TInputAction>) => {
12+
const action$ = merge(store.action$, effect(store.action$, store.state$));
13+
const state$ = action$.pipe(
14+
scan((state: TState, action: TInputAction) => store.reducers[action.type](state, action.payload), store.initialState),
15+
startWith(store.initialState),
16+
);
17+
const newStore: Store<TState, TReducers, TInputAction> = {
18+
action$,
19+
state$,
20+
dispatch: store.dispatch,
21+
initialState: store.initialState,
22+
reducers: store.reducers,
23+
};
24+
return newStore;
25+
};
26+
27+
export const ofType = <TActionType extends string>(actionType: TActionType) => filter((action: Action<string, unknown>) => action.type === actionType);

lib/either.ts

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
export type Right<T> = { readonly isLeft: false; readonly get: T };
2+
export type Left<T> = { readonly isLeft: true; readonly get: T };
3+
export type Either<TLeft, TRight> = Left<TLeft> | Right<TRight>;
4+
5+
export function isLeft<TLeft, TRight>(either: Either<TLeft, TRight>): either is Left<TLeft> {
6+
return either.isLeft;
7+
}
8+
9+
export function isRight<TLeft, TRight>(either: Either<TLeft, TRight>): either is Right<TRight> {
10+
return !isLeft(either);
11+
}
12+
13+
export const right: <TRight>(right: TRight) => Right<TRight> = <TRight>(right: TRight) => ({
14+
isLeft: false,
15+
get: right,
16+
});
17+
18+
export const left: <TLeft>(left: TLeft) => Left<TLeft> = <TLeft>(left: TLeft) => ({
19+
isLeft: true,
20+
get: left,
21+
});
22+
23+
export const foldEither = <TLeft, TRight, TSeed>(mapLeft: (left: TLeft) => TSeed, mapRight: (right: TRight) => TSeed) => (either: Either<TLeft, TRight>) => {
24+
if (isRight(either)) {
25+
return mapRight(either.get);
26+
} else if (isLeft(either)) {
27+
return mapLeft(either.get);
28+
}
29+
};
30+
31+
export const mapEither = <TRight1, TRight2>(f: (val: TRight1) => TRight2) => <TLeft>(either: Either<TLeft, TRight1>) => {
32+
if (isRight(either)) {
33+
return right(f(either.get));
34+
} else if (isLeft(either)) {
35+
return either;
36+
}
37+
};
38+
39+
export const flatMapEither = <TRight1, TRight2, TLeft>(f: (val: TRight1) => Either<TLeft, TRight2>) => (either: Either<TLeft, TRight1>) => {
40+
if (isRight(either)) {
41+
return f(either.get);
42+
} else if (isLeft(either)) {
43+
return either;
44+
}
45+
};

lib/maybe.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export const foldMaybe = <T, U>(defaultValue: U, f: (get: T) => U) => (maybe: Ma
2121
return result;
2222
};
2323

24-
export const maybe = <T>(get: T) => (get ? just(get) : nothing);
24+
export const maybe = <T>(get: T) => (get ? just(get) : nothing) as Maybe<T>;
2525
export const mapMaybe = <T, U>(f: (get: T) => U) => foldMaybe(nothing as Maybe<U>, (get: T) => just(f(get)));
2626
export const filterMaybe = <T>(f: (get: T) => boolean) => foldMaybe(nothing as Maybe<T>, (get: T) => (f(get) ? just(get) : nothing));
2727
export const flatMapMaybe = <T, U>(f: (get: T) => Maybe<U>) => foldMaybe(nothing, f);

lib/store.ts

+18-10
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,39 @@
11
import { Action } from "./action";
22
import { Subject, Observable } from "rxjs";
33
import { scan, startWith } from "rxjs/operators";
4-
import { Reducers, PayloadOfReducer } from "./reducer";
4+
import { PayloadOfReducer, Reducers } from "./reducer";
55

66
export type Dispatch<TReducers> = <TActionType extends keyof TReducers & string>(action: Action<TActionType, PayloadOfReducer<TReducers[TActionType]>>) => void;
77

8-
export type Store<TState, TReducers> = {
9-
action$: Observable<Action<string, PayloadOfReducer<TReducers[keyof TReducers]>>>;
8+
export type Store<TState, TReducers, TAction> = {
9+
action$: Observable<TAction>;
1010
state$: Observable<TState>;
1111
dispatch: Dispatch<TReducers>;
12+
reducers: TReducers;
13+
initialState: TState;
1214
};
1315

14-
export const createStore: <TState, TReducers>(initialState: TState, reducers: TReducers) => Store<TState, TReducers> = <TState, TReducers>(
16+
export const createStore: <TState, TReducers extends Reducers<TState, unknown>>(
17+
initialState: TState,
18+
reducers: TReducers,
19+
) => Store<TState, TReducers, Action<keyof TReducers & string, unknown>> = <
20+
TState,
21+
TReducers extends Reducers<TState, unknown>,
22+
TActionTypes extends keyof TReducers & string
23+
>(
1524
initialState: TState,
1625
reducers: TReducers,
1726
) => {
18-
const action$ = new Subject<Action<string, PayloadOfReducer<TReducers[keyof TReducers]>>>();
27+
const action$ = new Subject<Action<TActionTypes, unknown>>();
1928
const state$ = action$.pipe(
20-
scan(
21-
(state: TState, action: Action<string, PayloadOfReducer<TReducers[keyof TReducers]>>) => (reducers[action.type] as any)(state, (action as any).payload),
22-
initialState,
23-
),
29+
scan((state: TState, action: Action<TActionTypes, unknown>) => reducers[action.type](state, action.payload), initialState),
2430
startWith(initialState),
2531
);
2632
return {
33+
initialState,
2734
action$: action$.asObservable(),
2835
state$: state$,
29-
dispatch: <TActionType extends keyof TReducers & string>(action: Action<TActionType, PayloadOfReducer<TReducers[TActionType]>>) => action$.next(action),
36+
dispatch: <TActionType extends TActionTypes & string>(action: Action<TActionType, PayloadOfReducer<TReducers[TActionType]>>) => action$.next(action),
37+
reducers,
3038
};
3139
};

0 commit comments

Comments
 (0)