redux-thunk-routine
How to use it
npm install redux-thunk-routine
or
yarn add redux-thunk-routine
Motivation
I use redux
and redux-thunk
in my day-to-day work, and they do really great job to help manage the states of my application.
However, I find that I have to write a lot of boilerplate code for each asynchronous action, and it is even more tedious to add static type checking (i.e. use TypeScript).
That's why I created redux-thunk-routine
, a small libary to reduce the boilerplate and improve the support of static typing.
Elevator Pitch
- This libary can save your time (less boilerplate and better static typing)
- It can also generalize the flow of dispatching actions (example: Global Loading & Error State)
- There is no harm to your existing code when you add this library
- It comes with drop-in replacement to your hand-writen code as well as helpers to simplify more
- Test Coverage is 100%
Understand routine in 1 minute
So what is a routine
? Let's explain it with an example.
Imagine that we are creating an asynchronous action to fetch data using API, we might write the typical code like below:
// 1. Define Constants // These constants are used as action types;;;
// 2. Define Synchronous Action Creators // We are creating Flux Standard Actions; ; ; // Note: There is a simplied version below to define action creators using `redux-action` library.// But we still have to define the types manually for each of them;;;
// 3. Define thunk action creator // The outer function is called "Thunk Action Creator";
// 4. Handle actions in reducers // Define Types for actions; ; ; // Make a union type; // In each condition branch, the type of action will be inferred;
// 5. Dispatch the thunk action to start the asynchronous journey store.dispatchfetchDataid;
Looking at the example, we see that for every asynchronous action:
- We have to define 3 constants:
REQUEST
,SUCCESS
,FAILURE
- We have to define 3 synchronous action creators:
request
,success
,failure
- We have to write same logic flow of dispatching actions: request -> side effects -> success/failure
- We have to write a lot of type definitions to make the static type checking works
What if I tell you that there is a smart thing called routine
to wipe these repetitive work out?
If we rewrite the previous example using routine
, we can have a minimum example like below:
// 1. Define a routine; // 2. Get the thunk action creator; // 3. Write the reducer; // 4. Dispatch the thunk actionstore.dispatchfetchDataid;
API Explain
Define Routine
We only need to provide 2 basic information: type of success payload and a string as routine type.
;
Option: we can explicitly decalre the error type if need
When define a routine
, we have the following things automatically generated for us:
- 3 action types:
routine.REQUEST
routine.SUCCESS
routine.FAILURE
- 3 synchronous action creators:
routine.request()
routine.success()
routine.failure()
- 3 methods to match actions (also as type guards):
routine.isRequestAction()
routine.isSuccessAction()
routine.isFailureAction()
Get Thunk Action Creator
When use helper function, we just need to provide the routine and the async function to create success payload:
;
Note: if you have multiple arguments to pass here, you can pack them as an object. (e.g., {arg1: 1, arg2: 'B'}). It sounds like "named arguments" in some other languages, and it is easier to infer type signature of the generated Thunk Action in this way.
It is equal to use the synchronous action creators manually:
;
Obviously, we write much less code using the helper function, but you can always fallback when you have such needs.
Apart from that, the helper function getThunkActionCreator
also provides an optional 3rd parameter which allows us to provide more options to overwrite how the payload for request action and failure action are generated:
;
Get Typed Action Payload In Reducer
We can easily get typed action payload by using type guard match functions (recommend):
;
Or we can use the getXXX
functions:
;
Reducer Signature
By using redux-thunk-routine
, the type of payloads are already checked when dispatching, so it is safe to put Action<any>
when define the reducer:
;
If needed, strictly adding types of actions is also possible:
; ;
getState
with getThunkActionCreator
?
How to use Well, there is a debate of using getState
in action creators and it was indexed in this blog.
IMO, it is better to let the component to select all required states before dispatching a thunk action. Because the flow looks simpler in this way and it is easier to test.
According to that, there is no getState
paramater passed to the executor function when using getThunkActionCreator
.
However, if you need to call getState
in some cases, you can do it by defining another thunk action creator to dispatch the thunk action created by routine
:
// Imagine we defined a thunk action creator from routine; // We could write another thunk action creator to access the existing state;
Abort/Cancel thunk action
Since version 1.1.0
, if you use getThunkActionCreator
to create your ThunkAction
, then the Promise
returned by the ThunkAction
can be aborted.
The code below is a simple example to show how to abort the execution of ThunkAction
:
// `fetchData` is the thunk action creator; // Abort with default reason ('Aborted')promise.abort; // Abort with reasonpromise.abort'I abort it';
When aborted, the Promise
will be rejected with an AbortError
, and the AbortError
will be dispatched as the payload of failure action.
Under the ground, it is using AbortablePromise
from simple-abortable-promise.
If your getSuccessPayload
function returns an AbortablePromise
, then that Promise
will be aborted as well when the ThunkAction
is aborted. Otherwise, the logic of getSuccessPayload
will still be executed, and the result will be ignored.
Other benefits of using routines
When using routine
, we are enforced to follow a common pattern to name our action types, and we are sure that each routine has the same flow of dispatching actions. That gives us a huge advantage if we want to pull out repetitive logic in reducers.
For example, we can implement a global loading reducer and a global error reducer based on regular expression to match action types, then remove the branches of dealing "REQUEST" and "FAILURE" actions in other reducers.
Quick example here:
// We start with defining the LoadingState // The shape of LoadingState is a hashmap-like object// Example: {// FETCH_BLOG: true,// FETCH_USER: false,// }//; // Then we write the reducer to handle the logic of changing the global loading state ; // Then we can write some selectors to use it // Select whether any routine is loading; // Select whether a given routine is loading; // Select whether any of a given set of routines is loading;
There are more details in this blog.
FAQ
Can I use this library in JavaScript projects without introducing TypeScript?
Yes, the library itself is written in TypeScript and compiled to ES6 with a .d.ts
file for type definition.
That is, it still can reduce boilerplate when use in a plain JavaScript project.
Can I use this library in my current project? How hard it is to migrate existing code?
Sure, this library is a very thin abstraction layer built on top of redux-thunk
, and there is no conflict to the foundation library. That is, it is totally harmless to add this library to your current project.
When introducing new library, I would suggest to start using it for new features only to test if it fits your needs before considering to migrate existing codebase. Once you have some experience with it, the migration path should be clear.
Can I extend the routine to add other actions?
Of course. As discussed in this issue, it is easy to create a subclass of ReduxThunkRoutine
.
For example, the code below demonstrates how to add TRUNCATE
as part of your routine.
// imports;; // Create the subclass
Then we can use it like this:
// Create routine; // Dispatch TRUNCATE actiondispatchroutine.truncate; // Access the action name directly in reducerswitch action.type { //... case routine.TRUNCATE: // Do stuff break;} // Use match helper function in reducerif routine.isTruncateActionaction
Acknoledgement
This library uses redux-actions to create Flux Standard Actions
This library is inspired by redux-saga-routines.