2.1.1 • Public • Published


    Rinter is a minimalist state container based on reactive extensions.


    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").

    Application displaying a number and two buttons: plus and minus

    An immutable object describes the application state. And the actions generate a new instance of the application state:

    A diagram displaying an action called increment that creates a new 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.

    Diagram of the Rinter architecture

    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 }) {
      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}}
    console.log(controller.state); // {a: {count: 1}, b: {count:0}}

    API Reference




    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';

    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) {

    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.




    npm i rinter

    DownloadsWeekly Downloads






    Unpacked Size

    23.1 kB

    Total Files


    Last publish


    • dfernandez79