kilto
v0.4.1
Published
A state management system with easy async and low boilerplate.
Downloads
9
Maintainers
Readme
Kilto
Kilto is a state management solution with a similar API to Redux but with a paradigm that saves you from the the boilerplate that can make Redux feel excessively beurucratic.
Kilto has 0 dependencies and is puny, around 1kb gzipped. It works on both Node.js and in browsers back to IE8. To use async actions, you'll need to polyfill ES6 Promise in older browsers.
With Kilto you usally keep all of your application state in a single "store". When you want to interact with your state you dispatch a pure function that returns a new state based on the old state, rather that mutating it. This makes it very easy to derive your UI purely from the state.
Kilto's design includes a built-in paradigm to make async actions just as easy to write as synchronous ones, while still using pure functions of state and remaining predictable.
Contents
API
The API is extremely simple to learn and teach in a matter of minutes.
import { createStore } from 'kilto';
const initialState = {
greeting: 'Hello world!',
counter: 0
};
const store = createStore(initialState);
console.log(store.getState() === initialState);
// true
Easy enough, now we have a store which contains state. It's pretty lame if the state never changes, so lets dispatch an update:
const up = (state, value = 1) => {
return {
...state,
counter: state.counter + value
};
};
const down = (state, value = 1) => {
return {
...state,
counter: state.counter - value
};
};
store.dispatch(up, 5);
console.log(store.getState());
// {
// greeting: 'Hello world!',
// counter: 5
// }
We dispatch pure functions, called actions, which return a new state based on the old state. Unlike Redux, there is no reducer, no action creator, no action types. Just actions. This reduces boilerplate and enforces better separation of concerns.
Pure functions are functions which don't mutate any values, they return a new value based exclusively on the parameters. Every time you call a pure function with the same parameters, it will return the same thing every time. This makes state predictable.
Notice how when up
is called, the value parameter is passed the 5
from the dispatch
call. The first argument up
receives is the previous state, then comes the rest of the arguments used to call dispatch
.
Now lets get notified when our state changes:
const printCounter = (newState) => {
console.log('Current counter:', newState.counter);
};
store.subscribe(printCounter);
store.dispatch(down);
// Current counter: 4
store.dispatch(down, 2);
// Current counter: 2
That's all you need to get started using Kilto, but what about the elephant in the pure functional room: async and side effects. What if our action is asynchronous, like saving data to the server? We can't just dispatch async functions, because they would introduce side effects and race conditions which would ruin the predictive nature of our state!
For this, Kilto provides an elegant solution out of the box to run asynchronous actions:
const fetchGreeting = [
async (state, target) => {
const response = await fetch(`http://totallyrealapi.com/get_greeting?target=${target}`);
const data = await response.json();
return data.greeting;
},
(state, greeting, target) => {
return {
...state,
greeting: greeting
};
}
];
store.subscribe((state) => console.log('Current greeting: ', state.greeting));
store.dispatch(fetchGreeting, 'universe');
/* Request begins in async. */
store.dispatch(up);
/* Action runs and returns immediately */
// Current greeting: Hello world!
// Current counter: 3
/* After a delay: */
// Current greeting: Greetings universe!
// Current counter: 3
Kilto also lets us pass an array of functions into dispatch. Kilto will evaluate each one in order, and when a function returns, it's result is piped into the second argument of next function. But wait! The first function is async, so it returns a promise! Kilto sees that the return is a promise and subscribes for the result. When it's ready it pipes the value into the second argument of the next function. The first argument of each function is always the current state.
The last function in an action must be a synchronous (pure) function of state that returns the new state. This guarantees that every new state is a pure function of the previous state. Notice how when we dispatched up
it incremented the counter to 3
. Some time after that, the async action finishes it's fetch and the second function in the fetchGreeting action initiates a new state change with the new greeting. Notice how in the end neither update overwrote the other's changes.
For more on asynchronous actions, look here.
Installation
Install Kilto with
npm install --save kilto
The usual way to use Kilto is with the ES6 syntax and a bundler like webpack:
import { createStore, bindActions } from 'kilto'
// OR import just what you need (not needed in webpack 2, which does treeshaking by default)
import createStore from 'kilto/lib/createStore';
You can also include the umd build directly in the browser:
<script src="node_modules/kilto/umd/Kilto.min.js"></script>
<script>var store = Kilto.createStore(initialState)</script>
In Node.js, browserify, or webpack, you can use the require syntax.
const { createStore } = require('kilto');
// OR
const createStore = require('kilto/lib/createStore').default;
Philosophy
Kilto follows a similar philosophy as Redux. The whole state of your app should usually be in a single store, though there are times where it makes more sense to split the state up into multiple stores. The only way to change state is to dispatch a pure function that returns the next state. You can also dispatch an array of functions, and the last function will serve as the pure function that returns the next state. All updates should be done through dispatches, which then means that all subscribers will immediately be notified as soon as the state changes.
With Redux you need to 'pre-register' your actions with the store, that is, tell your store on creation how it should respond to every action type in the reducer. This leads to large reducers that have to be split and combined using combineReducer
or some other means. This is not necessary in Kilto, so managing a large single state tree is usually quite easy.
Kilto works great with immutable data. seamless-immutable is a great choice to ensure immutability, since it works entirely with standard javascript Objects and Arrays. Other options include immutable.js and mori. Even if you don't use a library to enforce that your data can't change without you knowing it, you should never mutate state!! This includes things like state.x = y
and state.z.push(a)
. Don't do it! Your subscribers won't know the state has changed.
When you do things right and don't mutate state, it makes your app predictable. The current state of your applciation can only change in calls to dispatch
, and whenever that is called and state is changed, every subscriber knows right away. This allows for a whole bunch of optimizations like O(1) time to compare huge objects.