Striking a Happy Medium with Redux & Typescript

Nick Galloway
Granular Engineering
4 min readSep 10, 2018

At Granular, we develop our frontend using TypeScript on top of JavaScript to provide a layer of sanity when dealing with hundreds of thousands of lines of code. Over the past six months, we have begun to dip our feet into the world of React as we began the transition from AngularJS to React/Redux. This has also required us to decide how we want to use TypeScript with React/Redux. Specifically, it required us to decide how much TypeScript we wanted to bring into the already tedious process of Redux.

Our first pass at bringing TypeScript to Redux was to create types for everything: every action type, every action creator, every action dispatcher, every reducer, etc. This provided us with great type information but caused about 50% more code in our actions.ts and reducers.ts files that consisted solely of type definitions. After this, we refactored several times in an attempt to reach an optimum balance between the effort & code bloat of creating & managing type definitions and the payoff of having type assistance available when developing & refactoring. Eventually, we arrived at a solution that provided us with a happy medium.

Here we will walk through this solution as defined in our ReduxTypes.ts file…

Actions

An action is simply an object that consists of a type and a payload.

Simple Action

If an action does not contain a payload, we call this a “simple action.”

At this point, you may question the need for “simple actions.” You may propose this:

Does this not suffice in covering both situations? The answer is “not quite”, and the reason will be explained when we get to “simple action creators.”

Any Action

The term “any action” describes an action with a type and an optional payload of the type any. We use the “any action” when the value of types is not worth the effort of writing and maintaining them. Specifically, this is used when typing the Reducer (shown later).

Action Creators

An “action creator” is a function that takes the payload of that action and returns to us an action object.

Simple Action Creator

If an action does not contain a payload, we create this action using what we call a “simple action creator.”

Ok, so why define these “simple actions” and “simple action creators”? Why not simply (pun intended) define one that works for both situations? Something like this:

Well, because this doesn’t work for both situations since the payload is never optional; it is always required to exist or required not to exist. When we have an action creator with a payload, we want the function to require the payload as the single parameter. However, when we have an action creator that does not require a payload, we want the function to require no parameters. In a similar fashion, we differentiate “simple actions” from “actions” because one action may require a payload while another may require no payload.

Action Creators as Props

An “action creator as a prop” is the definition of an action creator when injected into a component using MapDispatchToProps with connect from react-redux.

Simple Action Creators as Props

The same goes for the “simple action creator as a prop”:

Action Dispatchers

An “action dispatcher” is a function that dispatches one or more actions synchronously or asynchronously. At Granular, we use the redux-thunk middleware to provide us with this ability. The actual type definition for an action dispatcher would be relatively complex:

Instead, we decided to expose the pieces of the definition. This way, developers can choose how specific or generic they feel their action dispatcher needs to be typed.

This is also an example of a situation where we found typing everything to be extraordinarily tedious and the payoff to be minimal. Specifically, the parameter definition (x) of IDispatch can be anything from an action object to an action creator to an action dispatcher. Correctly typing that is a good amount of effort. On top of that, we discovered that Redux does very little type checking (if any) inside of dispatch, regardless of the generics provided to it. For these reasons, the above definitions work very well for us.

Action Dispatchers As Props

Following the nomenclature above, we also define a type for an “action dispatcher as a prop”:

Simple Action Dispatchers

There is also a “simple action dispatcher”:

Reducers

Finally, we arrive at the type definition for a reducer:

With reducers, we found that typing the state or piece of state that the reducer managed was relatively easy and highly beneficial to developers. However, we also found that providing the exact definition of every possible action that could come through the reducer was tedious and provided very little payoff as we use a switch statement which matches against the already-defined enums that define the action types. For these reasons, this definition of a reducer has worked very well for us.

Summary

Our team did quite a few refactors, carefully evaluating the value of each type definition, before we arrived at the approach presented here. What we came out with is a happy medium between the costs of TypeScript and the value it provides. This may not work for every team, but we feel it will work well for most. It encourages rapid development velocity while also providing strong confidence in the code.

--

--