Type-safe Flux Standard Actions (FSA) in React Using TypeScript FSA
TypeScript FSA (previously known as Redux TypeScript Actions) is a library that helps you define actions with types and use those actions while enjoying the compiler's help. Those actions can be synchronous or asynchronous. FSAs and the library's benefits and usage are covered in this article.
What is the FSA (Flux Standard Action)?
After Facebook released their Flux architecture/pattern, many libraries implemented the Flux philosophy. Redux is one the popular libraries and it got inspiration from the Flux and other sources.
Flux can be divided into several concepts Dispatcher, Store, Action, and View. As the title of this blog post suggests, I am focusing on the action part.
From the Flux Concepts -> examples page:
Actions define the internal API of your application. They capture the ways to interact with your application. They are simple objects that consist of a "type" field and data.
the specification would lead to the following object:
{
type: 'ADD_TODO',
text: 'TODO content'
}
The only problem with this simple example is that the developer could choose any property name for the value. All the following are valid names: title, name, text, todoName, etc. It is impossible to know what property to expect from the example Redux reducer. This is something that TypeScript's type system library and the TypeScript FSA can help with.
Asynchronous actions add an extra level of complexity. What should be the shape of the success response? How about failure? There are so many valid options that there is a big risk that everyone does different implementations.
The FSA is a lightweight specification that defines the structure of an action. FSA compliance helps developers create abstractions that can work with different Flux implementations.
I strongly recommend reading the Flux Standard Action design document because it is a short read with valuable information.
TypeScript FSA—Defining and dispatching an action
This library has two purposes: to provide utility functions for defining FSA-compliant actions and consume them (in, for example, the Redux reducers) without losing the type system provided by the TypeScript compiler.
Let's have a look at a usage example.
In the action creator TypeScript file, I exported a new action (of type number), which can be called with productId. Note that the string PRODUCT_SET can be defined in-place; there is no need to add it to the constants folder.
export const setProductFilter = actionCreator<{ groupId: number }>('PRODUCT_FILTER_SET')
What is the dispatching event like?
Pretty awesome. Adding typings at crucial points (actions, component props, etc.) allows the clever TypeScript compiler to help the developer.
TypeScript FSA—Reducer
As the old saying goes, "GIF image tells more than a thousand words."
For those who can't see the GIF, here is the source code:
if(isType(action, setCardFilter)) {
action.payload.filter
}
I had automatic completion for the action in the GIF. I first got the payload, then the groupId
and its type.
How does the compiler know which payload I'm using?
The TypeScript FSA contains a function called isType that takes the received action and action creator as arguments. I had to read the implementation of isType a few times to really understand it. Without getting too deep into generics and the type system, the basic idea is that the function says that the action is Action
where P is the type of the action creator.
export declare function isType<P>(action: AnyAction, actionCreator: ActionCreator<P>): action is Action<P>;
The TypeScript compiler is so sophisticated, it knows that the action inside the if-statement block (if(isType(action, setCardFilter)) { }
) must be a card filter action by looking the setCardFilter. This language feature is called Type Guard.
Conclusion & further reading
Using TypeScript FSA decreases failure points by allowing the clever compiler to do its job: figuring out which operations are legal.
In future blog posts, I'll dive deeper into asynchronous actions and unit testing.
Do you prefer Redux Saga over Redux Thunk? There is a companion package for Redux Saga. If generators (used in the Redux Saga) are not your cup of tea, wait for my upcoming blog post, where I'll show examples of Redux Thunk used together with TypeScript FSA.
David Philipson created a library called TypeScript FSA Reducers that allows developers to write reducers in fluent syntax while maintaining type safety.