Skip to content

Instantly share code, notes, and snippets.

@Jpunt
Last active October 4, 2018 11:44
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Jpunt/db8b286d3b881d489afdc06841ade55d to your computer and use it in GitHub Desktop.
Save Jpunt/db8b286d3b881d489afdc06841ade55d to your computer and use it in GitHub Desktop.
This is how I like to do Redux stuff in TypeScript. Good coverage, with minimal effort and redefining types. What do you think?
/**
* Types
*/
// IState
import rootReducer from "./reducers";
export type IState = {
// Maps the return type of each reducer into its return-type
[K in keyof typeof rootReducer]: ReturnType<typeof rootReducer[K]>
}
// Action
import { MagicNumberActionTypes } from "./magic-number-actions";
// imports of more action types...
export type Action =
| MagicNumberActionTypes
// more action types...
// Dispatch: could potentially call action-creators that are thunked, and thus need state (our state)
import { Dispatch as ReduxDispatch } from "redux";
export type Dispatch = ReduxDispatch<IState>;
// ActionCreator types
import { ActionCreator } from "redux";
import { ThunkAction } from "redux-thunk";
// Type of an action creator that is a thunk, but doesn't return a promise
export type SyncThunkActionCreator<ReturnType> = ActionCreator<ThunkAction<ReturnType, IState, void>>;
// Type of an action creator that is a thunk that returns a promise
export type AsyncThunkActionCreator<ReturnType> = ActionCreator<ThunkAction<Promise<ReturnType>, IState, void>>;
/**
* Reducers
*/
// ./reducers/index.ts
import magicNumber from "./magic-number-reducer";
// imports of more reducers...
const rootReducer = {
magicNumber,
// more properties with reducers...
};
export default rootReducer;
// ./reducers/magic-number-reducer.ts
import { Action } from "../types";
type State = number | null;
export default function(state: State = null, action: Action): State {
switch (action.type) {
case "MAGIC_NUMBER_SUCCESS":
return action.magicNumber;
case "MAGIC_NUMBER_REQUEST":
case "MAGIC_NUMBER_FAILURE":
return null;
default:
return state;
}
}
/**
* Action creators
*/
// ./actions/magic-number-actions.ts
import { AsyncThunkActionCreator } from "../types";
export const getMagicNumber: AsyncThunkActionCreator<void> = () => {
return async (dispatch) => {
dispatch(magicNumberRequest());
try {
const magicNumber = await MagicNumberApi.get();
dispatch(magicNumberSuccess(magicNumber));
} catch (error) {
dispatch(magicNumberFailure(error));
}
};
};
// Plain action creators that are used above
function magicNumberRequest(): {type: "MAGIC_NUMBER_REQUEST"} {
return {type: "MAGIC_NUMBER_REQUEST"};
}
function magicNumberSuccess(payload: number): {type: "MAGIC_NUMBER_SUCCESS", magicNumber: number} {
return {type: "MAGIC_NUMBER_SUCCESS", magicNumber};
}
function magicNumberFailure(error: HttpError): {type: "MAGIC_NUMBER_FAILURE", error: Error} {
return {type: "MAGIC_NUMBER_FAILURE", error};
}
// Export all available types
export type MagicNumberActionTypes =
| ReturnType<typeof magicNumberRequest>
| ReturnType<typeof magicNumberSuccess>
| ReturnType<typeof magicNumberFailure>
;
/**
* Container components
*/
// ./components/MagicNumber/MagicNumberContainer.ts
import { connect } from "react-redux";
import { IState, Dispatch } from "../types";
import { GetMagicNumber } from "../actions/magic-number-actions";
import MagicNumber from "./MagicNumber";
interface IStateProps {
magicNumber: IState["magicNumber"];
}
const mapStateToProps = (state: IState): IStateProps => ({
magicNumber: state.magicNumber,
});
interface IDispatchProps {
getMagicNumber: () => void;
}
const mapDispatchToProps = (dispatch: Dispatch): IDispatchProps => ({
getMagicNumber: () => dispatch(getMagicNumber()),
});
export default connect(mapStateToProps, mapDispatchToProps)(MagicNumber);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment