Upgrade action typing with typesafe-actions

This commit is contained in:
James Greenaway
2019-02-12 17:59:55 +00:00
parent 3f14fcc161
commit e482e989de
12 changed files with 59 additions and 93 deletions

View File

@@ -12,6 +12,7 @@
"redux": "^4.0.1", "redux": "^4.0.1",
"redux-observable": "^1.0.0", "redux-observable": "^1.0.0",
"rxjs": "^6.4.0", "rxjs": "^6.4.0",
"typesafe-actions": "^3.0.0",
"typescript": "3.2.4" "typescript": "3.2.4"
}, },
"scripts": { "scripts": {

View File

@@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { Dispatch } from 'redux'; import { Dispatch } from 'redux';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { Action, State } from '../../state'; import { RootAction, RootState } from '../../state';
import { cancel } from '../../state/workloads/actions'; import { cancel } from '../../state/workloads/actions';
import { WorkloadItem, WorkloadItemStateProps } from '../WorkloadItem'; import { WorkloadItem, WorkloadItemStateProps } from '../WorkloadItem';
@@ -36,11 +36,11 @@ const WorkloadList: React.SFC<WorkloadListProps> = ({ workloads, cancelWorkload
); );
const mapStateToProps = (state: State): WorkloadListStateProps => ({ const mapStateToProps = (state: RootState): WorkloadListStateProps => ({
workloads: Object.values(state.workloads), workloads: Object.values(state.workloads),
}); });
const mapDispatchToProps = (dispatch: Dispatch<Action>): WorkloadListDispatchProps => ({ const mapDispatchToProps = (dispatch: Dispatch<RootAction>): WorkloadListDispatchProps => ({
cancelWorkload: (id: number) => dispatch(cancel({ id })), cancelWorkload: (id: number) => dispatch(cancel({ id })),
}) })

View File

@@ -5,7 +5,7 @@ import { Provider } from 'react-redux';
import { createEpicMiddleware } from 'redux-observable'; import { createEpicMiddleware } from 'redux-observable';
import moment from 'moment'; import moment from 'moment';
import { reducer, epics, Action, State } from './state'; import { reducer, epics, RootAction, RootState } from './state';
import * as WorkloadActions from './state/workloads/actions'; import * as WorkloadActions from './state/workloads/actions';
import './index.css'; import './index.css';
import App from './components/App'; import App from './components/App';
@@ -14,7 +14,7 @@ import App from './components/App';
// @ts-ignore: use Redux devtools if installed in browser // @ts-ignore: use Redux devtools if installed in browser
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const epicMiddleware = createEpicMiddleware<Action, Action, State>(); const epicMiddleware = createEpicMiddleware<RootAction, RootAction, RootState>();
const store = createStore(reducer, composeEnhancers(applyMiddleware(epicMiddleware))); const store = createStore(reducer, composeEnhancers(applyMiddleware(epicMiddleware)));
epicMiddleware.run(epics); epicMiddleware.run(epics);

View File

@@ -1,10 +0,0 @@
import * as WorkloadsActions from './workloads/actions';
import { Action as WorkloadsAction } from './workloads/actions';
export type Action = WorkloadsAction;
export const Actions = {
WorkloadsActions,
};
export default Actions;

View File

@@ -4,4 +4,4 @@ import { epics as workloadsEpics } from './workloads';
export const epics = combineEpics(workloadsEpics); export const epics = combineEpics(workloadsEpics);
export default epics; export default epics;

View File

@@ -1,3 +1,2 @@
export * from './reducer'; export * from './reducer';
export * from './actions'; export * from './epics';
export * from './epics';

View File

@@ -1,17 +1,16 @@
import { combineReducers } from 'redux'; import { combineReducers } from 'redux';
import { Action } from './actions'; import { WorkloadsAction, WorkloadsState, workloadReducer } from './workloads';
import {
State as WorkloadsState,
reducer as workloadReducer,
} from './workloads';
export interface State { export type RootAction =
| WorkloadsAction;
export interface RootState {
workloads: WorkloadsState; workloads: WorkloadsState;
} }
export const reducer = combineReducers<State, Action>({
export const reducer = combineReducers<RootState, RootAction>({
workloads: workloadReducer, workloads: workloadReducer,
}); });

View File

@@ -1,60 +1,21 @@
import { createAction } from 'typesafe-actions';
import { Status } from './types'; import { Status } from './types';
import { SUBMIT, CREATED, CANCEL, UPDATE_STATUS } from './constants';
export type Action = {
type: 'WORKLOAD_SUBMIT';
payload: {
complexity: number;
};
} | {
type: 'WORKLOAD_CREATED';
payload: {
id: number;
complexity: number;
completeDate: Date;
status: Status;
};
} | {
type: 'WORKLOAD_CANCEL';
payload: {
id: number;
};
} | {
type: 'WORKLOAD_UPDATE_STATUS';
payload: {
id: number;
status: Status;
};
};
export const submit = ({ complexity }: { complexity: number }): Action => ({ export const submit = createAction(SUBMIT, resolve => (params: { complexity: number }) => resolve({ complexity: params.complexity }));
type: 'WORKLOAD_SUBMIT',
payload: {
complexity,
},
});
export const created = ({ id, status, complexity, completeDate }: { id: number, status: Status, complexity: number, completeDate: Date }): Action => ({ export const created = createAction(CREATED, resolve =>
type: 'WORKLOAD_CREATED', (params: { id: number, status: Status, complexity: number, completeDate: Date }) => resolve({
payload: { id: params.id,
id, status: params.status,
status, completeDate: params.completeDate,
completeDate, complexity: params.complexity,
complexity, }));
},
});
export const cancel = ({ id }: { id: number }): Action => ({ export const cancel = createAction(CANCEL, resolve => (params: { id: number }) => resolve({ id: params.id }));
type: 'WORKLOAD_CANCEL',
payload: {
id,
},
});
export const updateStatus = ({ id, status }: { id: number, status: Status }): Action => ({ export const updateStatus = createAction(UPDATE_STATUS, resolve =>
type: 'WORKLOAD_UPDATE_STATUS', (params: { id: number, status: Status }) => resolve({ id: params.id, status: params.status }))
payload: {
id,
status,
},
});

View File

@@ -0,0 +1,4 @@
export const SUBMIT = 'workload/SUBMIT';
export const CREATED = 'workload/CREATED';
export const CANCEL = 'workload/CANCEL';
export const UPDATE_STATUS = 'workload/UPDATE_STATUS';

View File

@@ -1,11 +1,12 @@
import { combineEpics, Epic, ofType } from 'redux-observable'; import { combineEpics, Epic, ofType } from 'redux-observable';
import { map, tap, ignoreElements } from 'rxjs/operators'; import { filter, map, tap, ignoreElements } from 'rxjs/operators';
import { isActionOf } from 'typesafe-actions';
import { Action } from '../actions'; import { RootAction, RootState } from '../reducer';
import { State } from '../reducer'; import * as workloadsActions from './actions';
type AppEpic = Epic<Action, Action, State>; type AppEpic = Epic<RootAction, RootAction, RootState>;
// import { WorkloadService } from './services'; // import { WorkloadService } from './services';
@@ -16,7 +17,7 @@ type AppEpic = Epic<Action, Action, State>;
const logWorkloadSubmissions: AppEpic = (action$, state$) => ( const logWorkloadSubmissions: AppEpic = (action$, state$) => (
action$.pipe( action$.pipe(
ofType('WORKLOAD_SUBMIT'), filter(isActionOf(workloadsActions.submit)),
map(action => action.payload), map(action => action.payload),
tap((payload) => console.log('Workload submitted', payload)), tap((payload) => console.log('Workload submitted', payload)),
ignoreElements(), ignoreElements(),

View File

@@ -1,23 +1,29 @@
import { Action } from './actions'; import { ActionType, getType } from 'typesafe-actions';
import { Status } from './types';
interface Entry<Id extends number> { import { Status } from './types';
import * as workloadActions from './actions';
export type WorkloadsAction = ActionType<typeof workloadActions>
interface WorkloadEntry<Id extends number> {
id: Id; id: Id;
complexity: number; complexity: number;
completeDate: Date; completeDate: Date;
status: Status; status: Status;
} }
export type State = { export type WorkloadsState = {
[Id in number]: Entry<Id>; [Id in number]: WorkloadEntry<Id>;
}; };
const initialState: State = {}; const initialState: WorkloadsState = {};
export const reducer = (state: State = initialState, action: Action): State => { export const workloadReducer = (state: WorkloadsState = initialState, action: WorkloadsAction): WorkloadsState => {
switch (action.type) { switch (action.type) {
case 'WORKLOAD_CREATED': case getType(workloadActions.created):
return { return {
...state, ...state,
[action.payload.id]: { [action.payload.id]: {
@@ -28,7 +34,7 @@ export const reducer = (state: State = initialState, action: Action): State => {
}, },
}; };
case 'WORKLOAD_UPDATE_STATUS': case getType(workloadActions.updateStatus):
return { return {
...state, ...state,
[action.payload.id]: { [action.payload.id]: {

View File

@@ -9431,6 +9431,11 @@ typedarray@^0.0.6:
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
typesafe-actions@^3.0.0:
version "3.0.0"
resolved "https://npm.nutterlogic.com/typesafe-actions/-/typesafe-actions-3.0.0.tgz#8b162ea11fa7383d4b4d96afc181118e84e06e10"
integrity sha512-NLpRc/FY+jPfWL0aUXQzjxPyF0Xug2om6akaoRLQ18KGwP2yYNBJu9vkv2q1q+Cx/+edy2Qf6O8xXnYY/xwz1A==
typescript@3.2.4: typescript@3.2.4:
version "3.2.4" version "3.2.4"
resolved "https://npm.nutterlogic.com/typescript/-/typescript-3.2.4.tgz#c585cb952912263d915b462726ce244ba510ef3d" resolved "https://npm.nutterlogic.com/typescript/-/typescript-3.2.4.tgz#c585cb952912263d915b462726ce244ba510ef3d"