Using Redux with Typescript for superior type safety
The benefits of Typescript in a team environment for building and maintaining production-worthy codebases are nothing new. At Cleo we committed early to developing our web and mobile apps with Typescript to give us the safety of static typing when developing our product.
One issue we faced building SPAs with Typescript was additional boilerplate when combined with other paradigms, specifically state management and Redux. The Redux docs[1] give a good starting point for Typescript integration, but their approach remains convoluted. We wanted a way of simplifying this integration to minimise the effort of achieving full type coverage within our codebase.
Our issue
Using Redux to manage state, but typescript can lead to additional boilerplate so type safety less likely to be used
Typescript shines when used everywhere in the codebase
Various state refactors and migrations (e.g. redux-thunk → redux-sagas) only benefit from typescript coverage when the underlying state is fully typed
Solution by example
We'll create a simple app for displaying a customer's transactions to show how we can create type safety alongside Redux.
State
We define a new type for our transaction, and the store's state.
We use a switch statement on the action's type, and the getType helper to extract the action's type. This allows the payload's type to be correctly inferred & used within the scope.
// reducer.ts
import * as transactionListActions from './actions'; import { ActionType, getType } from 'typesafe-actions'; import { Category, Transaction } from "../types";
export const transactionListReducer = (state: TransactionListState, action: ActionType<typeof transactionListActions>): TransactionListState => { switch (action.type) { case getType(transactionListActions.setTransactionCategory): //getType evaluates to 'SET_TRANSACTION_DESCRIPTION' // Payload is now typed correctly const { transactionId, categoryId } = action.payload; ... case getType(transactionListActions.deleteTransaction): // Payload is now typed correctly const transactionId = action.payload; ...
default: return state; } };
The following shows the payload type suggestions we get when within a case.
Selectors
Simply type the state
// selectors.ts
import { TransactionListState } from "./reducer"; import { Transaction } from "../types";
...and not run out of wardrobe. Whether you're a sustainable fashion follower, a trend-setter, or just someone who loves a little bit of style, you've probably heard of Depop. And if you haven't, we're about to enlighten you on the modern way to buy, sell, and discover stylish clothing. Depop is like an Ebay for fashion, but with an interface like Instagram. It's a modern fashion marketplace app that makes it easy to connect both Depop sellers and buyers so that fashion is more accessible, inclusive, diverse, and less wasteful.
Now, we expected some kind of shift in spending, you know, given the world is literally on it’s ass right now. But these. These are some weirdly iconic ways of coping with stress – here are the outlets with your biggest % increase in spending since Feb.
Slowly but surely, normality is rearing its head into this whole pandemic thing. As much as you might feel ready to get back to things, you’ve made some strong relationships in the last few months. Seriously strong, like your HUGE commitment to food delivery strong. Consider this an ode to your takeout spending as you move on to the next chapter of your life. Restaurants.