rinter

2.1.1 • Public • Published

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

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

Dependents (1)

Package Sidebar

Install

npm i rinter

Weekly Downloads

2

Version

2.1.1

License

MIT

Unpacked Size

23.1 kB

Total Files

19

Last publish

Collaborators

  • dfernandez79