rinter
v2.1.1
Published
Minimalist state container based on reactive extensions
Downloads
29
Maintainers
Readme
Rinter
Rinter is a minimalist state container based on reactive extensions.
Installation
yarn add rinter rxjs
Getting Started
Rinter is similar to Redux, MobX or Vuex: it handles the application state in a centralized and predictable way.
To get started we are going to follow the usual example of incrementing or decrementing a number (aka "Counter").
An immutable object describes the application state. And the actions generate a new instance of the application state:
Rinter represents this architecture with an object called
Controller. A controller has a state
property that
returns the current state value. It also has methods to modify the state. The
view is able to detect changes by the changes
property.
Code (using the controller function):
const counter = controller({
initialState: { count: 0 },
mutators: {
increment: state => ({ count: state.count + 1 }),
decrement: state => ({ count: state.count - 1 }),
},
});
const appCounter = counter();
appCounter.changes.subscribe(state => {
// renderView is an example of how the view will respond to state
// changes and send callbacks to update the state
renderView(state, {
onIncrementClick: appCounter.increment,
onDecrementClick: appCounter.decrement,
});
});
The controller function is a shortcut to write less code. If you prefer ES6 classes you can create a Controller by sub-classing DefaultController:
class Counter extends DefaultController {
constructor(initialValue = { count: 0 }) {
super(initialValue);
}
increment() {
this.set({ count: this.state.count + 1 });
}
decrement() {
this.set({ count: this.state.count - 1 });
}
}
const appCounter = counter();
appCounter.changes.subscribe(state => {
renderView(state, {
onIncrementClick: () => appCounter.increment(),
onDecrementClick: () => appCounter.decrement(),
});
});
Controller Composition
As your application grows, you may want to compose multiple controllers into one. The compose function does that:
const twoCounters = compose({
a: counter,
b: counter,
});
const controller = twoCounters();
console.log(controller.state); // {a: {count: 0}, b: {count:0}}
controller.a.increment();
console.log(controller.state); // {a: {count: 1}, b: {count:0}}
API Reference
Functions
Classes
Troubleshooting
Log State Changes
The Observable returned by the changes
property can be used to trace state
changes:
controller.changes.subscribe(state => console.log(state));
However, setting up this in an app can be annoying. The good news is that you can use the debug utility function to trace state changes:
import { debug } from 'rinter';
//...
debug(controller);
By default, debug will log every state change, but you can mute it:
debug(controller, debug.SILENT);
Also you may want to customize the logging behavior:
debug(controller, {
stateChange(value) {
myCustomLogFunction(value);
},
});
The debug function returns the controller that you pass to it:
const controller = debug(createController());
If you pass a controller factory function, debug will detect it and return a factory function too:
const createController = debug(initialCreateController);
const controller = createController();
Which is handy when using compose:
const twoCounters = compose({
a: debug(counter),
b: counter,
});
Bundle Size
Rinter itself is small, but RxJS is a big module. If your bundle size is big, make sure to use a bundler that supports ES6 modules and does tree-shaking to remove unnecessary code. For example, Webpack 4+ or Rollup supports that, but Webpack 3 doesn't.
License
MIT