terse-reduce
v2.0.14
Published
Monadic library to help with Typescripts --strictNullChecks
Downloads
1
Maintainers
Readme
Terse-Reduce
Javascript / Typescript state management with a special emphasis on:
- easy integration with and incremental migration from traditional Redux reducers.
- handling immutable updates through a mutable API powered by Immer
- minimal boilerplate & maximum readability
- strong typesafety for Typescript and strong autocompletion / refactoring support for Javascript (VSCode highly recommended)
Installation
yarn add terse-reduce
Examples
NOTE: All below code is written in Typescript. Javascript users can simply remove all Typescript interface
and type
declarations
Example 1: with sinlge 'root' reducer
import { createStore, Store } from 'redux';
import { manageRoot, initialize } from 'terse-reduce';
import axios from 'axios';
interface AppState { num: number, msg: string }
interface ApiError { status: number; message: string; }
const calculator = manageRoot<AppState>()
.startWithState({
num: 0,
msg: ''
})
.processActions(take => ({
ADD: take<number>()
.update(_ => _.change.num += _.payload),
SUBTRACT: take<number>()
.update(_ => _.change.num -= _.payload),
ADD_PI: take<number>()
.update(_ => _.change.msg = `Calculating PI to ${_.payload} decimal places...`)
.promise(_ => axios.get<number>(`${apiUrl}/calculate/pi?decimal-places=${_.payload}`))
.update(_ => { _.change.num += _.promiseResult.data; _.change.msg = ''; })
.catch<ApiError>(_ => _.promiseError.status === 400)
.update(_ => _.change.notificationMessage = _.promiseRejection.message)
})
});
const { reducer, initialState } = initialize<AppState>(() => store.dispatch, calculator);
const store = createStore(reducer, initialState);
calculator.ADD.dispatch(5);
Example 2: with multiple 'branch' reducers
This is effecively the same as using 1 reducer per branch and then applying combineReducers()
interface Branch1State { propStr: string; propBool: boolean; }
interface Branch2State { propNum: number; propArr: number[]; }
interface AppState { branch1: Branch1State, branch2: Branch2State }
const branch1Manager = manageBranch<AppState, Branch1State>('branch1')
.startWithState({
propStr: '',
propBool: false
})
.processActions(take => ({
ACTION_ONE: take<string>()
.update(_ => _.change.propStr = _.payload)
}));
const branch2Manager = manageBranch<AppState, Branch2State>('branch2')
.startWithState({
propNum: 0,
propArr: []
})
.processActions(take => ({
ACTION_TWO: take<number>()
.update(_ => _.change.propNum = _.payload)
}));
const { reducer, initialState } = initialize<AppState>(() => store.dispatch, branch1Manager, branch2Manager);
const store: Store<AppState> = createStore(reducer, initialState);
branch1Manager.ACTION_ONE.dispatch('Bake Bread');
Example 3: Creating a re-usable chain of operations
Sometimes, one might want to create a re-usable chain of operations to be performed.
This is useful for highly repetitive logic, particularly when promises are involved.
For this we have takeRoot()
and takeBranch()
functions. Below is an example of takeRoot()
.
import { manageRoot, initialize, takeRoot } from "../src";
import { createStore, Store } from 'redux';
import axios from 'axios';
interface User { id: number, name: string, age: number }
interface Company { id: number, name: string }
interface AppState { showUiLoader: boolean, notificationMessage: string, users: User[], companies: Company[] }
interface ApiError { status: number; message: string; }
const postToApi = <P extends any[]>(endpoint: string, getArray: (s: AppState) => P) =>
takeRoot<AppState, P>()
.promise(_ => axios.post<P>(`/api/${endpoint}`, { body: _.payload }))
.update(_ => getArray(_.change).push(_.promiseResult))
.catch<ApiError>(_ => _.promiseError.status === 400)
.update(_ => _.change.notificationMessage = _.promiseRejection.message);
const manageState = manageRoot<AppState>()
.processActions(take => ({
SAVE_USER: postToApi('/user', s => s.users),
SAVE_COMPANY: postToApi('/company', s => s.users)
}))