# Motivation
The main motivation of creating Retux is that we need a TypeScript Redux architecture that reduces boilerplate code while still being declarative. The current norm which surrounds Action Creators is mostly borrowed from the JavaScript realm. We know TypeScript can do more. That is why we redesigned from the ground up with type-first in mind.
# Problems of the Official Recipes
Redux officially supports TypeScript typings. Yay! But wait, after reading the recipes (opens new window) on the docs it seems even more cumbersome than JavaScript.
You ended up writing something like this:
// src/store/system/types.ts
export const UPDATE_SESSION = 'UPDATE_SESSION'
interface UpdateSessionAction {
type: typeof UPDATE_SESSION
payload: SystemState
}
export type SystemActionTypes = UpdateSessionAction
// src/store/system/actions.ts
import { SystemState, UPDATE_SESSION, SystemActionTypes } from './types'
export function updateSession(newSession: SystemState): SystemActionTypes {
return {
type: UPDATE_SESSION,
payload: newSession
}
}
We still write the boilerplate code like in JavaScript and now plus type definitions.
In Retux Actions of a module are defined only once in a central hub called Action Catalog which will be reused to generate other utilities including Action Creators.
# Problems of Other Typescript Guide
One of the most popular thrid-party solutions for Redux in TypeScript is the react-redux-typescript-guide (opens new window).
To some degree it solves the redundance issue, but in the cost of dark magic.
switch (action.type) {
case getType(todos.add):
return [...state, action.payload];
...
This is a clever solution to reuse code but it also brings obscurity. getType(todos.add)
works because the todos.add
function properties are modified by typesafe-actions (opens new window). This is fine if everyone in the team are on the same page. For folks who prefer the KISS rule(Keep It Simple Stupid), Retux reuses Actions instead of Action Creators so no need to introduce extra magic. Also see Dan's post (opens new window) on clean code.
Then in the example on Action Creators (opens new window):
export const increment = () => action(INCREMENT);
export const add = (amount: number) => action(ADD, amount);
export const emptyAction = createAction(INCREMENT)<void>();
export const payloadAction = createAction(ADD)<number>();
export const payloadMetaAction = createAction(ADD)<number, string>();
export const payloadCreatorAction = createAction(
'TOGGLE_TODO',
(todo: Todo) => todo.id
)<string>();
This solves the boilerplate issue but the code is not clear enough for others to read. This is due to the Action declaration is mixing with the Action Creator's implementation. As project scales it is not easy for team members to take a glance and know what actions is provided and how to use them.
How about a separated documentation? Nah. Documentations are destined to be left unmaintained.
See how Action Catalog offers the same benefits of Flux constant style (opens new window) and how to generate Action Creators with createActionCreators
.
# Problems of State Sharing
As the store scales in Redux it is recommended to split it into different reducers and combine them with combineReducers
.
Many users later want to try to share data between two reducers, but find that
combineReducers
does not allow them to do so. There are several approaches that can be used:
Now you either go down the reducer-hell with reduce-reducers
or move logic to middlewares. If you move only the necessary logic to middlewares(a.k.a fat reducer) then your codebase will look scattered which is hard for others to follow; or if for consistency you move most of the logic to middlewares(fat middleware) then the reducer is all boilerplate code.
Another drawback of combining reducers is performance related. With N reducers it will be N times slower on handling each Action. This may not be trivial for projects with many reducers and frequent dispatch
.
Sharing states in Retux is simple because modules are desgined to be flexible on splitting and merging with low cost. See Action Handlers and Combine Action Handlers.
# Get Rid of Boilerplate Action Creators
Boilerplate action creators are those who do nothing but simply return an action.
function addTodo(text) {
return {
type: ADD_TODO,
text
}
}
There are two main reasons why action creators exist in Redux:
Redux relies on
string
to distinguish action types. But to JavaScript compiler,ACTION1
andACTIONI
are no different - they are allstring
. So if you mistypeACTION1
asACTIONI
(and you will, according to Murphy's Law), no compile-time error is yelled. But when the action is wrapped in a function whose name should you mistyped, the compiler/linter can now correctly catch the error.Flux constant style (opens new window) is another solution for this by assigning Actions names to constants.
With TypeScript this is not an issue because we can declare actual
ACTION1
andACTION2
types.With middlerwares like Redux Thunk or Redux Promise, an Action Creator can be easily swapped later on to gain async ability.
This is true if you use middlerwares that introduce mixed action types. For others like Redux Saga or Redux Observable which implement Process Manager pattern, raw Actions are actually preferred in TypeScript. They are simpler to write and faster to run.
Retux also has solution to strictly type mixed action types. See the Redux Thunk and Redux Promise guide.
Nevertheless, boilerplate Action Creators got to go. Redux offers createActionCreators
and proxyActionCreators
for easy generating Action Creators.