Minimal unidirectional global state management library (based on basic functional programming concepts).
Description • Demo • Getting started • Minimal example • Concepts • Example with Ramdajs
Ortum means "the rise" or "origin" which resonates with the functionality of the library with regards to functional state management
Ortum is a small framework agnostic, unidirectional state management library.
It's inspired by the work of André Staltz' on
Profunctor State Optics
Ortum allows your features to be independent from the global state while having
the ability to use the global state.
By using this, your features are decoupled from their environment, thus easy to
test.
A demo can be found here: https://stackblitz.com/edit/ortum-counter?file=src/counter.ts
Install into your project:
$ npm install --save ortum
const { SimpleStateContainer, useProfunctor } = require('ortum');
const [appProf, onStateChange] = useProfunctor(
new SimpleStateContainer({ counter: 0 }),
);
onStateChange((state) => console.log(state));
console.log(appProf.getState()); // { counter: 0 }
appProf.setState(
({ counter, ...state }) => ({...state, counter: counter + 1 })
);
const counterProf = appProf.promap(
(state) => state.counter,
(counter, state) => ({ ...state, counter })
);
console.log(authorsProf.getState()); // 2
Don't get scared away by the functional jargon that's being used. They are just terms for patterns that you already use.
Something on which you can call map
over with a single function to transform
each element.
For example an array:
[1,2,3,4].map(x => x * x) // [2,4,9,16]
Something on which you can call promap
over, however, you can pass two
functions:
- a map (
x => x * 2
), times two - a 'reverse'-map (
x => x / 2
), divide by two
This is some pseudo-code explaining what happens:
const prof1 = new Profunctor([1,2,3,4]);
const prof2 = prof1.promap(x => x * 2, x => x / 2)
prof2.get() // [2,4,6,8]
// ^ the first function applied on the array of prof1
prof2.set(([first, ...rest]) => [10, ...rest])
// ^ the second function applied on the array of prof2
prof2.get() // [10,4,6,8]
prof1.get() // [5,2,3,4]
// this code is for explanatory purpose, not an example of Ortum
This is what happens:
- A Profunctor
prof1
holds some state[1,2,3,4]
and is 'promappable'. - When promapping you get a new Profunctor
y
. prof2
now holdsprof1.map(x => x * 2)
asget
-function and a reversed version asset
-function
The same concept can be applied on an object:
const prof1 = new Profunctor({ counter: 0, title: 'Profunctors' });
const prof2 = prof1.promap(
obj => obj.counter,
(counter, obj) => ({ ...obj, counter })
)
// this code is for explanatory purpose, not an example of Ortum
You guessed it; prof2
holds only the value of counter
: 0
. And the
set
-function, puts it back in the prof1
-state.
Ramdajs is a nice functional library that can remove a lot of boilerplating from
your promap
-functions.
Because the signatures of Ortum's promap
-functions have a 'data-last'
approach, it works fluently with other functional libraries that use currying.
import * as R from 'ramda';
import { useProfunctor, SimpleStateContainer } from 'ortum';
const appProf = useProfunctor(
new SimpleStateContainer({ foo: { bar: { baz: 'value' } } })
)
const bazLens = R.lensProp(['foo','bar','baz']);
const bazProf = appProf.promap(
R.view(bazLens),
R.set(bazLens)
)
bazProf.getState() // 'value'
bazProf.setState('another value')
bazProf.getState() // 'another value'
Instead of making the two map/unmap functions manually, we can use lenses
with
view
and get
from Ramda to simplify our code. This would be the alternative:
appProf.promap(
state => state.foo.bar.baz,
(baz, state) => {
...state,
foo: {
...state.foo,
bar: {
...state.foo.bar,
baz
}
}
}
)