Redux & TypeScript
Benefits
TypeScript has the potential to bring the following benefits to a Redux application:
- Type safety for reducers, state and action creators
- Easy refactoring of typed code
- A superior developer experience in a team environment
Installing
To get all the benefits of types you should install the type definition file for the React Redux library. The other libraries (Redux and Redux Thunk) install with their type definitions and React Redux will likely do the same in the near future.
npm install --save-dev @types/react-redux
Usage
Each of the following sections demonstrates how TypeScript can be used with the various parts of Redux.
State
export interface StoreState {
name: string;
enthusiasmLevel: number;
}
or
export interface ProjectState {
loading: boolean;
projects: Project[];
error: string | undefined;
page: number;
}
export interface AppState {
projectState: ProjectState;
}
Actions & Action Creators
export const INCREMENT_ENTHUSIASM = 'INCREMENT_ENTHUSIASM';
export type INCREMENT_ENTHUSIASM = typeof INCREMENT_ENTHUSIASM;
export interface IncrementEnthusiasm {
type: INCREMENT_ENTHUSIASM;
}
export type EnthusiasmAction = IncrementEnthusiasm | DecrementEnthusiasm;
export function incrementEnthusiasm(): IncrementEnthusiasm {
return {
type: INCREMENT_ENTHUSIASM,
};
}
export const LOAD_PROJECTS_REQUEST = 'LOAD_PROJECTS_REQUEST';
export const LOAD_PROJECTS_SUCCESS = 'LOAD_PROJECTS_SUCCESS';
export const LOAD_PROJECTS_FAILURE = 'LOAD_PROJECTS_FAILURE';
export const SAVE_PROJECT_REQUEST = 'SAVE_PROJECT_REQUEST';
export const SAVE_PROJECT_SUCCESS = 'SAVE_PROJECT_SUCCESS';
export const SAVE_PROJECT_FAILURE = 'SAVE_PROJECT_FAILURE';
export const DELETE_PROJECT_REQUEST = 'DELETE_PROJECT_REQUEST';
export const DELETE_PROJECT_SUCCESS = 'DELETE_PROJECT_SUCCESS';
export const DELETE_PROJECT_FAILURE = 'DELETE_PROJECT_FAILURE';
interface LoadProjectsRequest {
type: typeof LOAD_PROJECTS_REQUEST;
}
interface LoadProjectsSuccess {
type: typeof LOAD_PROJECTS_SUCCESS;
payload: { projects: Project[]; page: number };
}
interface LoadProjectsFailure {
type: typeof LOAD_PROJECTS_FAILURE;
payload: { message: string };
}
...
export type ProjectActionTypes =
| LoadProjectsRequest
| LoadProjectsSuccess
| LoadProjectsFailure
| SaveProjectRequest
| SaveProjectSuccess
| SaveProjectFailure
| DeleteProjectRequest
| DeleteProjectSuccess
| DeleteProjectFailure;
Reducers
export function reducer(
state: StoreState = initialState,
action: EnthusiasmAction
): StoreState {
switch (action.type) {
case INCREMENT_ENTHUSIASM:
return { ...state, enthusiasmLevel: state.enthusiasmLevel + 1 };
case DECREMENT_ENTHUSIASM:
return {
...state,
enthusiasmLevel: Math.max(1, state.enthusiasmLevel - 1),
};
}
return state;
}
export function projectReducer(
state = initialProjectState,
action: ProjectActionTypes
) {
switch (action.type) {
case DELETE_PROJECT_REQUEST:
return { ...state };
case DELETE_PROJECT_SUCCESS:
return {
...state,
projects: state.projects.filter(
(project: Project) => project.id !== action.payload.id
),
};
case DELETE_PROJECT_FAILURE:
return { ...state, error: action.payload.message };
default:
return state;
}
}
Connect (React Redux)
export function mapStateToProps({ enthusiasmLevel, name }: StoreState) {
return {
enthusiasmLevel,
name,
};
}
export function mapDispatchToProps(dispatch: Dispatch<EnthusiasmAction>) {
return {
onIncrement: () => dispatch(incrementEnthusiasm()),
onDecrement: () => dispatch(decrementEnthusiasm()),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Hello);
function mapStateToProps(state: AppState): ProjectState {
return {
...state.projectState,
};
}
const mapDispatchToProps = {
onLoad: loadProjects,
onSave: saveProject,
};
export default connect(mapStateToProps, mapDispatchToProps)(ProjectsPage);
Redux Thunk
export function loadProjects(
page: number
): ThunkAction<void, ProjectState, null, Action<string>> {
return (dispatch: any) => {
dispatch({ type: LOAD_PROJECTS_REQUEST });
return projectAPI
.get(page)
.then((data) => {
dispatch({
type: LOAD_PROJECTS_SUCCESS,
payload: { projects: data, page },
});
})
.catch((error) => {
dispatch({ type: LOAD_PROJECTS_FAILURE, payload: error });
});
};
}