Skip to content

Instantly share code, notes, and snippets.

@zerkalica
Last active November 5, 2015 07:23
Show Gist options
  • Save zerkalica/19d7fe14a489586cbce7 to your computer and use it in GitHub Desktop.
Save zerkalica/19d7fe14a489586cbce7 to your computer and use it in GitHub Desktop.
/* @flow */
type BaseAction = {
type: string
}
type ReduxReducer<TState, TAction> = (state: TState, action: TAction & BaseAction) => TState
class Reducer<TState, TAction> {
reduce: ReduxReducer<TState, TAction>;
constructor() {
this.reduce = this._reduce.bind(this)
}
_reduce(state: TState, action: BaseAction & TAction): TState {
// $FlowFixMe
const method = this[action.type]
return method ? method.call(this, state, action) : state
}
}
class Store<K, I, R, S> {
items: {[id: K]: I};
constructor(items: {[id: K]: I}) {
this.items = items
}
/* eslint-disable no-unused-vars */
clone(items: {[id: K]: I}): S {
throw new Error('abstract method')
}
createItem(rec: R, v?: I): I {
throw new Error('abstract method')
}
getItemKey(v: I): K {
throw new Error('abstract method')
}
/* eslint-enable no-unused-vars */
set(k: K, v: I): S {
const newItems: {[id: K]: I} = {...this.items}
newItems[k] = v
return this.clone(newItems)
}
has(k: K): boolean {
return this.items[k] !== undefined
}
get(k: K): I {
return this.items[k]
}
delete(k: K): S {
const obj: {[k: K]: any} = {}
obj[k] = undefined
const newItems: {[id: K]: I} = {...this.items, ...obj}
return this.clone(newItems)
}
replace(k: K, rec: R): S {
const item = this.createItem(rec, this.items[k])
return this.set(k, item)
}
filter(fn: (v: I) => boolean): S {
const newItems = {}
Object.keys(this.items).forEach(key => {
const item = this.items[key]
if (fn(item)) {
newItems[key] = item
}
})
return this.clone(newItems)
}
map(fn: (v: I) => I): S {
const newItems: {[id: K]: I} = {}
Object.keys(this.items).forEach(key => {
newItems[key] = fn(this.items[key])
})
return this.clone(newItems)
}
fromArray(recs: Array<R>): {
keys: Array<K>,
store: S
} {
const items: {[id: K]: I} = {}
const keys: Array<K> = []
recs.forEach((rec: R) => {
const item = this.createItem(rec)
const key = this.getItemKey(item)
keys.push(key)
items[key] = item
})
return {
keys: keys,
store: this.clone(items)
}
}
}
function createId(): string {
return Date.now() + String(Math.round(Math.random() * 1000))
}
type TodoRec = {
id?: string,
complete?: boolean,
text?: string
}
class Todo {
id: string;
complete: boolean;
text: string;
constructor(rec: TodoRec) {
this.id = rec.id || createId()
this.complete = rec.complete || false
this.text = rec.text || ''
}
}
class TodoStore extends Store<string, Todo, TodoRec, TodoStore> {
clone(newItems: {[id: string]: Todo}): TodoStore {
return new TodoStore(newItems)
}
getItemKey(todo: Todo): string {
return todo.id
}
createItem(rec: TodoRec, oldTodo?: Todo): Todo {
return new Todo({...oldTodo || {}, ...rec})
}
}
type TodosRec = {
store?: TodoStore,
keys?: Array<string>,
selectedTodo?: ?string,
serverError?: ?string
}
class Todos {
store: TodoStore;
keys: Array<string>;
selectedTodo: ?string;
serverError: ?string;
constructor(rec: TodosRec) {
this.store = rec.store || new TodoStore({})
this.keys = rec.keys || []
this.selectedTodo = rec.selectedTodo || null
this.serverError = rec.serverError || null
}
clone(rec: TodosRec): Todos {
return new Todos({...this, ...rec})
}
}
class TodoAddAction {
type: string = 'todoAdd';
text: string;
constructor(text: string) {
this.text = text
}
}
class TodoCompleteAction {
type: string = 'todoComplete';
id: string;
constructor(id: string) {
this.id = id
}
}
class TodoDestroyAction {
type: string = 'todoDestroy';
id: string;
constructor(id: string) {
this.id = id
}
}
class TodoAddMultipleAction {
type: string = 'todoAddMultiple';
items: Array<TodoRec>;
}
class TodoAddErrorAction {
type: string = 'todoAddError';
error: Error;
contructor(error: Error) {
this.error = error
}
}
class TodoAddMultipleErrorAction {
type: string = 'todoAddMultipleError';
error: Error;
contructor(error: Error) {
this.error = error
}
}
class TodoCompleteErrorAction {
type: string = 'todoCompleteError';
error: Error;
contructor(error: Error) {
this.error = error
}
}
class TodoDestroyErrorAction {
type: string = 'todoDestroyError';
error: Error;
contructor(error: Error) {
this.error = error
}
}
class TodoClearErrorsAction {
type: string = 'todoClearErrors';
}
type TodoReducerActions = TodoAddAction
| TodoCompleteAction
| TodoDestroyAction
| TodoAddMultipleAction
| TodoAddErrorAction
| TodoAddMultipleErrorAction
| TodoCompleteErrorAction
| TodoDestroyErrorAction
| TodoClearErrorsAction
class TodoReducer extends Reducer<Todos, TodoReducerActions> {
todoAddMultiple(todos: Todos, action: TodoAddMultipleAction): Todos {
const {store, keys} = todos.store.fromArray(action.items)
return todos.clone({
store,
keys,
selectedTodo: null
})
}
todoAddMultipleError(todos: Todos, {error}: TodoAddMultipleErrorAction): Todos {
return this._todoAnyError(todos, error)
}
todoAddError(todos: Todos, {error}: TodoAddErrorAction): Todos {
return this._todoAnyError(todos, error)
}
todoCompleteError(todos: Todos, {error}: TodoCompleteErrorAction): Todos {
return this._todoAnyError(todos, error)
}
todoDestroyError(todos: Todos, {error}: TodoDestroyErrorAction): Todos {
return this._todoAnyError(todos, error)
}
_todoAnyError(todos: Todos, error: Error): Todos {
return todos.clone({
serverError: error.message
})
}
/* eslint-disable no-unused-vars */
todoClearErrors(todos: Todos, action: TodoClearErrorsAction): Todos {
return todos.clone({
serverError: null
})
}
/* eslint-enable no-unused-vars */
todoAdd(todos: Todos, {text}: TodoAddAction): Todos {
const todo = todos.store.createItem({text})
return todos.clone({
store: todos.store.set(todo.id, todo)
})
}
todoComplete(todos: Todos, {id}: TodoCompleteAction): Todos {
return todos.clone({
store: todos.store.replace(id, {complete: true})
})
}
todoDestroy(todos: Todos, {id}: TodoDestroyAction): Todos {
return todos.clone({
store: todos.store.delete(id)
})
}
}
type AppAction = BaseAction & (
TodoReducerActions
)
type StateRec = {
todos?: Todos
}
class AppState {
todos: Todos;
constructor(rec: ?StateRec) {
const r: StateRec = rec || {}
this.todos = r.todos || new Todos({})
}
clone(rec: StateRec): AppState {
return new AppState({...this, ...rec})
}
}
class AppReducers {
todos: ReduxReducer;
constructor() {
this.todos = (new TodoReducer()).reduce
}
}
const initialState: AppState = new AppState()
const reducers: AppReducers = new AppReducers()
// register reducers and initialState in redux
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment