A Functional and Type-Safe approach to Redux
By using redux-fluent (https://github.com/Code-Y/redux-fluent).
What is redux-fluent?
Even though there are mixed opinions about it, redux still represents the de facto solution when it comes to state management in modern web applications, so you might want to read this article if you have ever experienced some of its drawbacks: redux-fluent is a tiny (3k) library built to enable a fully declarative, functional and type safe approach to redux.
Main Goals:
- Reduce boilerplate: Redux comes with the cost of a huge boilerplate, we normally need actions, action creators, action types, etc.
- λ Going Functional: Redux is per se a functional library, it embraces immutability, leverages pure functions and referential transparency and has a clear way of handling side-effects. However, there are few things that could be done better, such as (a) avoiding hypertrophic reducers (it’s easy to end up with reducers that do more than one thing and this breaks the single responsibility principle causing scaling-issues); (b) avoiding imperative control flows (different behaviours are normally glued together by switch-cases instead of a more readable function composition).
- Flux Standard Actions as first class concept, you may need a way to enforce some standards. https://github.com/redux-utilities/flux-standard-action
- Type Safety: We all want our code to be bullet proof, self documenting and able to provide meaningful introspection.
Introduction
During the course of this article we will take a look at how things are normally done in redux and what improvements could we leverage on by using redux fluent - https://github.com/Code-Y/redux-fluent.
// you can get started by:
yarn add redux-fluent redux flux-standard-action 1. Actions
Actions in redux are the basic entity to describe changes. You normally end up on having a file for action types (e.g. todos.action-types.js) and a file for action creators (e.g. todos.action-creators.js). In redux-fluent those two concepts are just squashed together, you have only actions - https://github.com/Code-Y/redux-fluent#createaction.
import { createAction } from 'redux-fluent';const addTodo = createAction('ADD_TODO');
console.log(addTodo.type); // ADD_TODOconst action = addTodo({ title: 'Write post on Medium' });
console.log(action); // { type: 'ADD_TODO', payload: '...' }
2. Reducers
Reducers in redux are the building blocks to handle state transitions, their interface is quite intuitive (State, Action) -> State. It’s easy for those reducers to grow uncontrollably in a real world scenario: Let’s imagine a TodoApp, it’d be reasonable to have a todos reducer which would embed the whole life cycle of the entire application (such as create, edit, delete, etc.). It’s indeed very rare for a reducer to respect the single responsibility principle, namely “do only one thing and do it right”. Complexity grows as the number of features grows since only one function handles the whole state life cycle. Not only testing and maintaining, but sometimes also building a mental representation of what’s going on becomes very difficult. In redux-fluent reducers are nothing more than function combinators - https://github.com/Code-Y/redux-fluent#createreducer.
import { createReducer, ofType } from 'redux-fluent';
import * as actions from './todos.actions.js';
import * as handlers from './todos.handlers.js';export const todosReducer = createReducer('todos')
.actions(
ofType(actions.addTodo).map(handlers.addTodo),
ofType(actions.deleteTodo).map(handlers.deleteTodo),
)
.default(
() => [],
);
No more endless switch-cases. What happens (and in which order) becomes clear at first glance because reducers in redux-fluent don’t contain any business logic, they only orchestrate which handlers respond to which actions.
3. Action Handlers
The core entity of redux-fluent, you can think of action handlers as micro-reducers: they are built with the single responsibility principle in mind: do-only-one-thing-and-do-it-right. Actual business logic is encapsulated in small and dedicated functions that are easy to test and compose - https://github.com/Code-Y/redux-fluent#oftype.
const addTodo = (state, { payload }) => state.concat(payload);4. Type Safety
redux-fluent ships with 100% type coverage for a beautiful developer experience: check it out https://github.com/Code-Y/redux-fluent#typescript-definitions
5. Flux Standard Action
FSA compliant actions is the normal output of redux-fluent action creators, you simply cannot do any different (https://github.com/redux-utilities/flux-standard-action).
Conclusions
Redux is great, it’ll continue to be the choice number one for a long time and redux-fluent is only one of the many way to handle state management in a scalable and structured way. Please, do reach out to me if you have any thought to share or something to star https://github.com/Code-Y/redux-fluent.