Cinammon Redux (Part 1)

We are going to implement our own flux architecture inspired by redux and and see how refacoring to patterns and some syntax sugar can reduce boilerplate mantaining type safety.

Working commented code here https://github.com/freddi301/cinammon-redux

Running examples here https://cinammon-redux.now.sh

// @flow

First of all let’s see a reducer

A very simple implementation follows

type CounterAction =
{ type: 'inc' }
| { type: 'add', payload: number }
;
type CounterState = number;
const CounterReducer: Reducer<CounterState, CounterAction> =
(state, action) => // implement the logic
{
switch (action.type) {
case 'inc': return state + 1;
case 'add': return state + action.payload;
default: throw new Error(`unknown action: ${action.type}`);
}
}

That’s it, but now we need a mutable object that will retain our state.

A simple observer pattern would do fine

export class Store<State, Action> {
state: State;
reducer: (state: State, action: Action) => State;
replaceReducer(reducer: (state: State, action: Action) => State) {
this.reducer = reducer;
};
listeners: Set<(state: State) => void> = new Set;
constructor(state: State, reducer: (state: State, action: Action) => State) {
this.state = state;
this.reducer = reducer;
}
publish = (action: Action): void => {
this.state = this.reducer(this.state, action);
this.notify();
}
notify() {
for (let listener of this.listeners) listener(this.state);
}
subscribe(listener: (state: State) => void): void {
this.listeners.add(listener);
}
unsubscribe(listener: (state: State) => void): void {
this.listeners.delete(listener);
}
}

As the flux architecture is mostly used with react lets create a high-order component to listen to the store. This convention will be followed.

import React from 'react';
export function connect<State, Action, Props>(
store: Store<State, Action>,
component: (props: Props, state: State
publish: (action: Action) => void) => React.Element<*>
): Class<React.Component<void, Props, { state: State }>> {
return class extends React.Component<void, Props, { state: State }> {
store: Store<State, Action> = store;
component: (
props: Props,
state: State,
publish: (action: Action) => void
) => React.Element<*> = component;
state = { state: store.state };
render() {
return this.component(
this.props,
this.state.state,
this.store.publish
);
}
listen = (state: State) => this.setState({ state });
componentWillMount() { this.store.subscribe(this.listen); }
componentWillUnmount() { this.store.unsubscribe(this.listen); }
}
}

We aren’t going to use internal state, instead we reify the domain and view state to the store. See also elm architecture.

const counterDemoStore = new Store(0, CounterReducer);
const CounterComponent = (props, state, publish) => <div>
counter = {state}<br/>
<button onClick={() => publish({ type: 'inc' })}>inc</button><br/>
</div>;
export const CounterDemo = connect(
counterDemoStore,
CounterComponent
);

More coming soon :)