Reckon JS is an event-based, immutable state container for javascript apps. Reckon manages state as Facebook Immutable objects. Reckon provides cursors, views and scoped events so that a single Reckon instance can be used for all state in an application.
The crux of this ReackonJS+React example is under examples/todomvc-react/js and view the demo application running here
Check out app.js for the starting point of the application, and the react components are in the '.jsx' files. The bundle.js is the compiled version of the code.
This is based on the TodoMVC template
Try it here
import Reckon from 'reckon-js';
let reckon = new Reckon({
fruits:['apple','pear','banana'],
veges:['carrot','broccoli','celery']
});
let fruitCursor = reckon.select('fruits');
let fruitJoinView = fruitCursor.addView('Fruit join', fruits => fruits.join());
fruitJoinView.onUpdate(newFruitJoin=>{
console.log("New fruit join: "+newFruitJoin);
});
fruitCursor.on('ADD_A_FRUIT', name => {
fruitCursor.update(fruitState => fruitState.push(name));
});
fruitCursor.emit('ADD_A_FRUIT','mandarin');
fruitCursor.emit('ADD_A_FRUIT','grape');
// output:
// New fruit join: apple,pear,banana,mandarin
// New fruit join: apple,pear,banana,mandarin,grape
To install stable version if you are using npm package manager
npm install reckon-js --save
Create a reckon object with your initial state
let reckon = new Reckon({
fruits: [
'apple',
'pear'
],
veges: [
'tomato',
'cucumber'
]
});
The state is made immutable with Immutable JS. Get back the state like this
let state = reckon.get();
state.get('fruits').get(0); //returns 'apple'
The reckon object doubles up as a cursor; you can use it like a cursor on the whole tree. See the next section for how cursors work
Cursors are used to manipulate a particular part of the state. Let's say we just want to deal with the fruits
let fruitsCursor = reckon.select('fruits');
fruitsCursor.get(); //returns ['apple','pear'] as an ImmutableJS wrapper
You can change the data through immutable updates, like this
fruitsCursor.update(currentState => {
return currentState.push('mandarin').set(1,'banana');
});
fruitsCursor.get(); //returns ['apple','banana','mandarin'] as immutable
For the full list of immutable opertions refer to the ImmutableJS Documentation.
The reckon object doubles up as a cursor on the whole state. For example, these two statements are equivelant
reckon.get()
reckon.select([]).get()
In fact you can treat the reckon object as a root cursor in all respects.
You can fire events on a cursor, and you can listen to events on the cursor.
fruitsCursor.on('ADD_FRUIT_EVENT', (data,fruits) => {
return fruits.concat(data);
});
fruitsCursor.emit('ADD_FRUIT_EVENT', ['orange','grapefruit']);
If your event listener returns anything, it is used to replace the current cursors state.
You could use an event listener and emit an event in order to update your state. But sometimes you just want to do an update. Here's a shortcut to simply update the state, under the covers it still fires events to do the update
myCursor.update(state=>{
return state.remove(3).concat([4,5,6]);
});
You can also listen for updates on the cursor. This will fire when anything in the cursors state changes, regardless of which cursor made the change
myCursor.onUpdate( (newState,oldState) => {
//do some stuff
});
Anything you return will be ignored though, so you don't end up with an infinite loop.
If you add a listener to a cursor, by default it listens ONLY to events fired on that cursor. However, what if you want to listen to events higher up the state tree, or lower down on the state tree?
import {filterTypes} from 'reckon-js';
...
let fruitSelect = reckon.select('fruit[0]');
fruitSelect.on('SCOPED_EVENT',()=>{
//this will be called because we passed the SUPER filter
},filterTypes.SUPER);
reckon.emit('SCOPED_EVENT');
The list of filters are:
- CURRENT (default) - listens to events fired on the same cursor, at the same path in the tree
- SUB - anything equal to or below on the state tree
- SUPER - anything equal to or above on the state tree
- ANY - listens to all events
- ROOT - events emitted only on the top of the tree
- SUB_EXCLUSIVE - listens to events strictly below the current cursor
- SUPER_EXCLUSIVE - listens to events strictly above the current cursor
- AFFECTED - anything where a change to the state in that cursor could affect the current cursor. i.e. anything above, below or the same as the current cursor
Views are simple functions that you add to a listener to expose a certain view of the data. You can also listen to updates on the view for when the output of the function changes.
let reckon = new Reckon({
fruit:'apple'
});
let view = reckon.addView('fruit_view',data=>data.get('fruit'));
view.onUpdate(data=>{
//data (which is returned by the view) will now be 'pear'
});
reckon.update(()=>{
return {
fruit:'pear'
};
});
You can go forward and backward in states by using reckon.undo() and reckon.redo()
This must be enabled when you create a reckon object in order to set the maximum number of states to remember (note that it only remembers the state changes to save on space). e.g.
let reckon = new Reckon({
state:'state-1'
},{
maxHistory:1000 //maximum number of undo's to store
});
reckon.update(state=>'state-2');
reckon.undo();
// State now contains 'state-1'
reckon.redo();
// State now contains 'state-2'
Note that updates are fired on all changes, including undo/redo.
Reckon is influenced by and takes cues from the following projects and programming paradigms:
Contributions are greatly appreciated, whether you're new to JS or experienced there are tasks available. Feel free to get in touch.
Issues and feature requests are also welcome here.
MIT