Design Goals
We know we could write a program directly in bytecode, and that everything else can be an example of "syntactic sugar", but isn't it life an endless attempt to improve "how we communicate with each other"?
Expressiveness
We've been writing redux-fluent with functional programming in mind, we wanted it as much declarative as it can be. We wanted to hide the "how this is happening" and surface the "what is happening". The application flow should be easy to grasp at first glance.
GIVEN I have a counter /**/
WHEN action is of type 'INCREMENT' /**/
THEN state is equal to state plus one /**/ const counter = createReduce('counter')
/**/ .actions(
GIVEN I have a counter /**/ ofType('INCREMENT').map((state) => state + 1),
WHEN action is of type 'DECREMENT' /**/ ofType('DECREMENT').map((state) => state - 1),
THEN state is equal to state minus one /**/ ofType('RESET').map(() => 0),
/**/ )
GIVEN I have a counter /**/ .default(() => 0);
WHEN action is of type 'RESET' /**/
THEN state is equal to zero /**/
Type Safety
Type Safety is a must have for a great developer experience, and we've been building redux-fluent with that in mind. We've got first-class support for typescript declarations so that you can benefit from it in your projects (whether they are on typescript or javascript).
λ Going Functional
Redux is a perfect fit for functional programming, it leverages on Immutability, makes it easy to think in terms of state machines and its architecture clearly defines how to handle side-effects so that your entire state management can be left pure.
However, in real world applications, reducers tend to easily grow uncontrolled and become unmaintainable. This is due to a reducer having to handle the whole life cycle of its state (embed update logic, case logic and handle default values).
We wanted to offer a pattern for splitting up reducers. A way to stop piling up switch-cases and start doing some function composition!
- Reducers as function combinators to build up complex behaviour by composing small functions together.
- Action Filters to nail down the case logic (the "which action to respond to").
- Action Handlers to granularly define transition logics (
A ~> B
).
import { ofType, createReducer } from 'redux-fluent';
export const fooReducer = createReducer('foo')
.actions(
// nail down to a specific action of type `bar`
ofType('bar')
// work out what's the new state value for actions of type bar
.map((state, action) => { /* do Something */ }),
)
// define default state value
.default(() => 'foobar');
FSA by design
Flux Standard Action represents the most established design pattern for shaping actions in a flux-like application. It enables interoperability so that it becomes possible to build extensions (read about the need of a standard).
redux-fluent is just built around FSA. That's it. Flux Standard Actions are first-class citizens in redux-fluent.
import * as actions from './todos.actions';
actions.addTodo({ title: 'Play some music' }, { date: new Date() });
{
type: 'ADD_TODO',
payload: { title: 'Play some music' },
meta: { date: 'Thu Dec 19 2019 19:05:16 GMT+0000 (Greenwich Mean Time)' },
}