elastic-store

0.2.0 • Public • Published

Introduction

A simple and flexible state container.

Features

  • generates state tree from action tree
  • supports late action binding
  • supports multi level state tree
  • attach/detach middlewares
  • middlewares can be attached to particular nodes of state tree
  • supports late middleware binding

Installation

$ npm install --save elastic-store

Usage

// ES6
import {Store} from "elastic-store";

// node
var Store = require("elastic-store").Store;

Walkthrough

import {Store} from "elastic-store";

// Create action tree
let actions = {
	todos: {
		// initial value
		init () {
			return [];
		},
		add (todos, newTodo) {
			return todos.concat([newTodo]);
		},
		remove (todos, id) {
			return todos.filter((todo) => { todo.id !== id});
		}
	}
};

// Create store with given actions
let store = Store(actions);

// Add new todo
store.dispatch("todos.add", {id: 1, task: "Demo dispatch"});


// Get todos
console.log(store());
// => {todos: [{id: 1, task: "Demo dispatch"}]}

// Remove a todo
store.dispatch("todos.remove", 1);

// Get todos
console.log(store());
// => {todos: []}


let clone = (data) => {
	return JSON.parse(JSON.stringify(data));
};

// A middleware to log changes in state
let logger = (actionPath, next, store) => {
	return (state, payload) => {
		console.log("Before:", clone(state));

		let newState = next(state, payload);

		console.log("After:", clone(newState);

		return newState;
	}
}


// Attach logger to 'todos.add' action
// so that it only logs state changes made by this action
let attachedLogger = store.attach("todos.add", logger);

store.dispatch("todos.add", {id: 2, task: "Demo middleware."});
// => 
// Before: {todos: []}
// After: {todos: [{id: 2, task: "Demo middleware."}]};

store.dispatch("todos.remove", 2); // won't log changes made by this action

// Detach logger
attachedLogger.detach();

// Apply logger globally so that state changes made by every actions is logged.
let attachedLogger = store.attach(logger);


store.dispatch("todos.add", {id: 2, task: "Demo middleware."});
// => 
// Before: {todos: []}
// After: {todos: [{id: 2, task: "Demo middleware."}]};

store.dispatch("todos.remove", 2);
// => 
// Before: {todos: [{id: 2, task: "Demo middleware."}]};
// After: {todos: []}


// Use this state to render UI?
// Create a renderer middleware
let renderer = (actionPath, next, store) => {
	return (state, payload) => {
		let newState = next(state, payload);

		aViewFramework.render(document.body, aComponent, newState);

		return newState;
	}
};


// Apply it globally
let attachedRenderer = store.attach(renderer);

// Now every changes to state are rendered
// To stop rendering the changes just detach the middleware
attachedRenderer.detach();

Actions and state

elastic-store generates state tree from action tree.

The action tree below generates the state tree that follows it.

// action tree
let actions = {
	todos: {
		allResolved: {
			// default value for this node
			init () { return false; },
			toggle (prevState) { ... }
		},
		items: {
			// default value for this node
			init () { return []; },
			add (notifications, newOne) { ... },
			remove (notifications, id) { ... },
	   }
    },
	notifications: {
		init () {
			return [];
		}
		add (notifications, newOne) { ... },
		dismiss (notifications, newOne) { ... }
   }
};

// state tree
{
	todos: {
		allResolved: false,
		items: []
	},
	notifications: []
}

Create Store

import {Store} from "elastic-store";

let store = Store(actions, middlewares, initialState);

Get state

The store instance is a function. Invoke it to get the state in it.

Example

import {Store} from "elastic-store";

let aStore = Store(actions, middlewares, initialState);
aStore(); // returns the state

Add actions

Actions can be added in two different ways.

1. While creating store

import {Store} from "elastic-store";

let store = Store(actions);

2. After creating store

astore.actions(actions);

Example

let commonActions = {...};

let store = Store(commonActions);


// attach new actions
let newActions = {...};
store.actions(newActions);

Dispatch action

Changes to state are made by dispatching messages to actions.

Syntax

store.dispatch(actionPath, payload);

Example

let actions = {
	todos: {
		add (state, payload) {
			return state.concat([payload]);
		}
	}
};

// dispatch
store.dispatch("todos.add", {id: 1, text: "Demo action dispatch."});

Middlewares

Middlewares in elastic-store are similar to that of Express.js.

Attach Middleware

Middlewares can be attached in two different ways:

1. While creating store

Middlewares attached while creating a store are globally applied.

let aStore = Store(actions, middlewares);

2. After creating store

Middlewares attached to an instance acts 3 different ways depending on how they were attached.

Acts globally

A middleware can be attached globally, so that it can act upon any actions.

let actions = {
	todos: {
		add (state, todo) {...},
		remove (state, id) {...}
	},
	trash: {
		add (state, todo) {...},
		restore (state, id) {..}
	}
};

let aStore = Store(actions);

let clone = (data) => {
	return JSON.parse(JSON.stringify(data));
};

// A middleware to log changes in state
let logger = (actionPath, next, store) => {
	return (state, payload) => {
		console.log("Before:", clone(state));

		let newState = next(state, payload);

		console.log("After:", clone(newState);

		return newState;
	}
}

// acts globally i.e. on
// todos.add
// todos.remove
// trash.add
// trash.restore
let attachedMiddleware = aStore.attach(logger);
Acts on particular state node

A middleware can be attached to a particular state node, so that it can act upon any actions within that node.

// acts on actions under 'todos' i.e.
// todos.add
// todos.remove
let attachedMiddleware = aStore.attach("todos", logger);
Acts on particular action

A middleware can be attached to a particular action, so that it reacts to that single action.

// acts on the action 'todos.add'
let attachedMiddleware = aStore.attach("todos.add", logger);

Detach Middleware

Middlewares are detachable.

attachedMiddlewar.detach();

Custom middleware

A middleware has following signature:

/*
* actionPath: Dot separated path to an action in the action tree
* action: The action at the action path, which changes data
* store: The store
*/
let aMiddleware = (actionPath, action, store) => {
	/*
	* state: The entire state tree, returned by store()
	* payload: The payload for the action
	*			e.g. 'somePayload' in 'store.dispatch("action.path", somePayload)'
	*/
	return (state, payload) => {
		//...

		let newState = action(state, payload);
		
		//...

		return newState;
	}
}

Setting initial values

Each state node can have initial value. Actions can have init() method which returns the initial value.

let actions = {
	todos: {
		// initial value for this node
		init () { return []; },
		add () { ... },
		remove () { ... }
	}
};

Setting initial state

Initial state of a store can be set while creating one.

let initialState = {
	todos: [
		{id: 1, todo: "Do something awesome."}
	]
};

let actions = {
	todos: {
		add () { ... },
		remove () { ... }
	}
};

let aStore = Store(actions, middlewares, initialState);

Readme

Keywords

Package Sidebar

Install

npm i elastic-store

Weekly Downloads

23

Version

0.2.0

License

ISC

Last publish

Collaborators

  • ludbek