├── docs ├── _config.yml ├── images │ ├── logo.png │ ├── logo_x2.png │ └── logo.svg ├── _site │ ├── images │ │ ├── logo.png │ │ ├── logo_x2.png │ │ └── logo.svg │ ├── css │ │ ├── common.css │ │ ├── landing.css │ │ ├── page.css │ │ └── syntax.css │ ├── index.html │ ├── react.html │ ├── middleware.html │ └── guide.html ├── css │ ├── common.css │ ├── landing.css │ ├── page.css │ └── syntax.css ├── index.md ├── _layouts │ ├── landing.html │ └── page.html ├── middleware.md ├── react.md └── guide.md ├── .gitignore ├── .npmignore ├── examples ├── dice-roller │ ├── .babelrc │ ├── package.json │ ├── index.html │ ├── stylesheet.css │ └── src │ │ └── index.js └── dice-roller-react │ ├── .babelrc │ ├── index.html │ ├── package.json │ ├── stylesheet.css │ └── src │ └── index.js ├── .babelrc ├── src ├── middleware │ ├── logger.js │ └── versioning.js ├── react.js └── index.js ├── test ├── middleware │ ├── versioning.js │ └── logger.js └── index.js ├── middleware ├── versioning.js └── logger.js ├── LICENSE ├── CHANGELOG.md ├── react.js ├── README.md ├── package.json └── index.js /docs/_config.yml: -------------------------------------------------------------------------------- 1 | baseurl: /elfi 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .vimproject 3 | yarn-error.log 4 | 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .vimproject 3 | yarn-error.log 4 | 5 | -------------------------------------------------------------------------------- /examples/dice-roller/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"] 3 | } 4 | -------------------------------------------------------------------------------- /docs/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madx/elfi/HEAD/docs/images/logo.png -------------------------------------------------------------------------------- /examples/dice-roller-react/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "react"] 3 | } 4 | -------------------------------------------------------------------------------- /docs/images/logo_x2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madx/elfi/HEAD/docs/images/logo_x2.png -------------------------------------------------------------------------------- /docs/_site/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madx/elfi/HEAD/docs/_site/images/logo.png -------------------------------------------------------------------------------- /docs/_site/images/logo_x2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madx/elfi/HEAD/docs/_site/images/logo_x2.png -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "targets": { 5 | "node": true, 6 | "browsers": "last 2 versions" 7 | } 8 | }], 9 | "react" 10 | ], 11 | 12 | "plugins": [ 13 | "transform-class-properties" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /examples/dice-roller-react/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |
23 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/docs/_layouts/page.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
25 |
26 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/react.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports.storeShape = exports.ElfiContext = undefined;
7 |
8 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
9 |
10 | exports.connect = connect;
11 |
12 | var _react = require("react");
13 |
14 | var _react2 = _interopRequireDefault(_react);
15 |
16 | var _propTypes = require("prop-types");
17 |
18 | var _propTypes2 = _interopRequireDefault(_propTypes);
19 |
20 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
21 |
22 | var ElfiContext = exports.ElfiContext = _react2.default.createContext(null);
23 |
24 | var storeShape = exports.storeShape = _propTypes2.default.shape({
25 | dispatch: _propTypes2.default.func.isRequired,
26 | getState: _propTypes2.default.func.isRequired,
27 | subscribe: _propTypes2.default.func.isRequired
28 | });
29 |
30 | function connect(WrappedComponent) {
31 | var mapStateToProps = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : function (storeState) {
32 | return {
33 | storeState: storeState
34 | };
35 | };
36 |
37 | function ConnectedComponent(props) {
38 | return _react2.default.createElement(
39 | ElfiContext.Consumer,
40 | null,
41 | function (store) {
42 | var propsFromStore = mapStateToProps(store.getState(), store);
43 | return _react2.default.createElement(WrappedComponent, _extends({}, props, propsFromStore, { store: store }));
44 | }
45 | );
46 | }
47 |
48 | ConnectedComponent.displayName = "Connect$" + WrappedComponent.name;
49 |
50 | return ConnectedComponent;
51 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # [](https://github.com/madx/elfi)
2 |
3 | > An elegant state container for your JavaScript applications
4 |
5 | [](https://github.com/madx/elfi/master/CHANGELOG.md)
6 | [](https://www.npmjs.com/package/elfi)
7 | [](https://discord.gg/cMkzd4J)
8 |
9 | *elfi* is a state container for JavaScript applications. It takes its roots in
10 | libraries such as [Flux][flux] and [Redux][redux], but strives to remain simple
11 | and avoid boilerplate code.
12 |
13 | It only takes a few minutes to [learn][doc:guide], works great with
14 | [Immutable.js][immutable] and [React][doc:react], and is easy to extend using
15 | [middleware][doc:middleware].
16 |
17 | ---
18 |
19 | **[Read the documentation][website]**
20 |
21 | ---
22 |
23 | ## Install
24 |
25 | ```console
26 | $ npm install elfi
27 | ```
28 |
29 | ## Usage
30 |
31 | **[Read the documentation][doc:guide]**
32 |
33 | ## Contributing
34 |
35 | * ⇄ Pull Requests and ★ Stars are always welcome.
36 | * For bugs and feature requests, please create an issue.
37 | * Pull Requests must ensure that automated checks pass (`$ npm run check`).
38 |
39 | ## [License](LICENSE)
40 |
41 | ---
42 |
43 | If interested, you can also read the (now quite obsolete) **[initial blog post][blogpost]**.
44 |
45 | ---
46 |
47 | [website]: http://madx.github.io/elfi/
48 | [blogpost]: http://madx.me/articles/a-simpler-alternative-to-flux-and-redux.html
49 | [flux]: https://github.com/facebook/flux
50 | [redux]: https://github.com/reactjs/redux
51 | [immutable]: https://facebook.github.io/immutable-js/
52 | [doc:guide]: docs/guide.md
53 | [doc:middleware]: docs/middleware.md
54 | [doc:react]: docs/react.md
55 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "elfi",
3 | "version": "2.0.2",
4 | "description": "An elegant state container for your JavaScript applications",
5 | "main": "./index.js",
6 | "scripts": {
7 | "clean": "rimraf index.js react.js middleware/",
8 | "lint": "eslint src/ test/",
9 | "test": "babel-tape-runner test/*.js test/**/*.js",
10 | "build": "babel src --out-dir .",
11 | "check": "npm run lint && npm run test",
12 | "prepare": "npm run clean && npm run check && npm run build"
13 | },
14 | "repository": {
15 | "type": "git",
16 | "url": "https://github.com/madx/elfi.git"
17 | },
18 | "files": [
19 | "index.js",
20 | "react.js",
21 | "middleware/*",
22 | "src/*",
23 | "docs/*"
24 | ],
25 | "keywords": [
26 | "flux",
27 | "redux",
28 | "state",
29 | "functional",
30 | "immutable"
31 | ],
32 | "author": "François Vaux
23 | 27 |31 | 32 |elfi is a state container for JavaScript applications. It takes its roots 28 | in libraries such as Flux and Redux, but strives to remain 29 | simple and avoid boilerplate code.
30 |
55 |
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/examples/dice-roller-react/src/index.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import ReactDOM from "react-dom"
3 | import {createStore} from "elfi"
4 | import createLoggerMiddleware from "elfi/middleware/logger"
5 | import {storeShape, Provider} from "elfi/react"
6 | import {Record, List} from "immutable"
7 |
8 | // Data types and constants
9 | const DICE = ["⚀", "⚁", "⚂", "⚃", "⚄", "⚅"]
10 |
11 | const AppState = Record({
12 | diceCount: 1,
13 | roll: List.of(d6())
14 | }, "AppState")
15 |
16 | function d6() {
17 | return DICE[~~(Math.random() * 6)]
18 | }
19 |
20 | // Changes
21 | const changes = {
22 | addDie(state, newDie) {
23 | if (state.diceCount === 6) {
24 | return state
25 | }
26 |
27 | return state
28 | .set("diceCount", state.diceCount + 1)
29 | .update("roll", r => r.push(newDie))
30 | },
31 |
32 | removeDie(state) {
33 | if (state.diceCount === 1) {
34 | return state
35 | }
36 |
37 | return state
38 | .set("diceCount", state.diceCount - 1)
39 | .update("roll", r => r.pop())
40 | },
41 |
42 | updateRoll(state, roll) {
43 | return state.set("roll", new List(roll))
44 | },
45 | }
46 |
47 | // React App
48 | function App(_, context) {
49 | const store = context.store
50 | const state = store.getState()
51 |
52 | function removeDie() {
53 | store.dispatch(changes.removeDie)
54 | }
55 |
56 | function rollDice() {
57 | const diceCount = state.diceCount
58 | const roll = Array.from({length: diceCount}, () => d6())
59 |
60 | store.dispatch(changes.updateRoll, roll)
61 | }
62 |
63 | function addDie() {
64 | store.dispatch(changes.addDie, d6())
65 | }
66 |
67 | return (
68 | {store.getState()}
22 | } 23 | 24 | Counter.propTypes = { 25 | store: storeShape.isRequired, 26 | } 27 | ``` 28 | 29 | ## `ElfiContext` and `connect` 30 | 31 | _elfi_ ships with an integration for the official React Context API that's been 32 | available since React 16.3.0. To use it, you must wrap your app in the context 33 | provider `ElfiContext.Provider` and you can use `ElfiContext.Consumer` in your 34 | child components to consume store data. 35 | 36 | You'll also probably want to automatically trigger updates using the `subscribe` 37 | method from the store. Here is a typical app startup code using _elfi/react_: 38 | 39 | ```js 40 | // app.js 41 | import React from "react" 42 | import ReactDOM from "react-dom" 43 | import { createStore } from "elfi" 44 | import { ElfiContext } from "elfi/react" 45 | 46 | import Counter from "./Counter" 47 | 48 | const store = createStore(0) // Our state is a simple integer 49 | const root = document.getElementById("app-root") 50 | 51 | document.addEventListener("DOMContentLoaded", renderApp) 52 | store.subscribe(renderApp) 53 | 54 | function renderApp() { 55 | ReactDOM.render( 56 |Current value is {value}
80 | } 81 | 82 | export default connect( 83 | Counter, 84 | value => ({ value }), 85 | ) 86 | ``` 87 | 88 | ## Dispatching actions from components 89 | 90 | Dispatching actions from your React components is no different than using _elfi_ 91 | outside of the React environment. To continue on our counter example, here's how 92 | you would increment the counter's value when clicking on it: 93 | 94 | ```js 95 | // Counter.js 96 | import React from "react" 97 | import PropTypes from "prop-types" 98 | import { storeShape, connect } from "elfi/react" 99 | 100 | // Our increment action 101 | function increment(state) { 102 | return state + 1 103 | } 104 | 105 | class Counter extends React.Component { 106 | static propTypes = { 107 | store: storeShape.isRequired, 108 | value: PropTypes.number.isRequired, 109 | } 110 | 111 | handleClick = () => { 112 | const { store } = this.props 113 | store.dispatch(increment) 114 | } 115 | 116 | render() { 117 | const { value } = this.props 118 | returnCurrent value is {value}
119 | } 120 | } 121 | 122 | export default connect( 123 | Counter, 124 | value => ({ value }), 125 | ) 126 | ``` 127 | 128 | You can [try this demo online](https://codepen.io/madx/pen/MBLvPg) 129 | -------------------------------------------------------------------------------- /docs/css/syntax.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Name: Base16 Default Light 4 | Author: Chris Kempson (http://chriskempson.com) 5 | 6 | Pygments template by Jan T. Sott (https://github.com/idleberg) 7 | Created with Base16 Builder by Chris Kempson (https://github.com/chriskempson/base16-builder) 8 | 9 | */ 10 | .highlight .hll { background-color: #e0e0e0 } 11 | .highlight { background: #f5f5f5; color: #151515 } 12 | .highlight .c { color: #b0b0b0 } /* Comment */ 13 | .highlight .err { color: #ac4142 } /* Error */ 14 | .highlight .k { color: #aa759f } /* Keyword */ 15 | .highlight .l { color: #d28445 } /* Literal */ 16 | .highlight .n { color: #151515 } /* Name */ 17 | .highlight .o { color: #75b5aa } /* Operator */ 18 | .highlight .p { color: #151515 } /* Punctuation */ 19 | .highlight .cm { color: #b0b0b0 } /* Comment.Multiline */ 20 | .highlight .cp { color: #b0b0b0 } /* Comment.Preproc */ 21 | .highlight .c1 { color: #b0b0b0 } /* Comment.Single */ 22 | .highlight .cs { color: #b0b0b0 } /* Comment.Special */ 23 | .highlight .gd { color: #ac4142 } /* Generic.Deleted */ 24 | .highlight .ge { font-style: italic } /* Generic.Emph */ 25 | .highlight .gh { color: #151515; font-weight: bold } /* Generic.Heading */ 26 | .highlight .gi { color: #90a959 } /* Generic.Inserted */ 27 | .highlight .gp { color: #b0b0b0; font-weight: bold } /* Generic.Prompt */ 28 | .highlight .gs { font-weight: bold } /* Generic.Strong */ 29 | .highlight .gu { color: #75b5aa; font-weight: bold } /* Generic.Subheading */ 30 | .highlight .kc { color: #aa759f } /* Keyword.Constant */ 31 | .highlight .kd { color: #aa759f } /* Keyword.Declaration */ 32 | .highlight .kn { color: #75b5aa } /* Keyword.Namespace */ 33 | .highlight .kp { color: #aa759f } /* Keyword.Pseudo */ 34 | .highlight .kr { color: #aa759f } /* Keyword.Reserved */ 35 | .highlight .kt { color: #f4bf75 } /* Keyword.Type */ 36 | .highlight .ld { color: #90a959 } /* Literal.Date */ 37 | .highlight .m { color: #d28445 } /* Literal.Number */ 38 | .highlight .s { color: #90a959 } /* Literal.String */ 39 | .highlight .na { color: #6a9fb5 } /* Name.Attribute */ 40 | .highlight .nb { color: #151515 } /* Name.Builtin */ 41 | .highlight .nc { color: #f4bf75 } /* Name.Class */ 42 | .highlight .no { color: #ac4142 } /* Name.Constant */ 43 | .highlight .nd { color: #75b5aa } /* Name.Decorator */ 44 | .highlight .ni { color: #151515 } /* Name.Entity */ 45 | .highlight .ne { color: #ac4142 } /* Name.Exception */ 46 | .highlight .nf { color: #6a9fb5 } /* Name.Function */ 47 | .highlight .nl { color: #151515 } /* Name.Label */ 48 | .highlight .nn { color: #f4bf75 } /* Name.Namespace */ 49 | .highlight .nx { color: #6a9fb5 } /* Name.Other */ 50 | .highlight .py { color: #151515 } /* Name.Property */ 51 | .highlight .nt { color: #75b5aa } /* Name.Tag */ 52 | .highlight .nv { color: #ac4142 } /* Name.Variable */ 53 | .highlight .ow { color: #75b5aa } /* Operator.Word */ 54 | .highlight .w { color: #151515 } /* Text.Whitespace */ 55 | .highlight .mf { color: #d28445 } /* Literal.Number.Float */ 56 | .highlight .mh { color: #d28445 } /* Literal.Number.Hex */ 57 | .highlight .mi { color: #d28445 } /* Literal.Number.Integer */ 58 | .highlight .mo { color: #d28445 } /* Literal.Number.Oct */ 59 | .highlight .sb { color: #90a959 } /* Literal.String.Backtick */ 60 | .highlight .sc { color: #151515 } /* Literal.String.Char */ 61 | .highlight .sd { color: #b0b0b0 } /* Literal.String.Doc */ 62 | .highlight .s2 { color: #90a959 } /* Literal.String.Double */ 63 | .highlight .se { color: #d28445 } /* Literal.String.Escape */ 64 | .highlight .sh { color: #90a959 } /* Literal.String.Heredoc */ 65 | .highlight .si { color: #d28445 } /* Literal.String.Interpol */ 66 | .highlight .sx { color: #90a959 } /* Literal.String.Other */ 67 | .highlight .sr { color: #90a959 } /* Literal.String.Regex */ 68 | .highlight .s1 { color: #90a959 } /* Literal.String.Single */ 69 | .highlight .ss { color: #90a959 } /* Literal.String.Symbol */ 70 | .highlight .bp { color: #151515 } /* Name.Builtin.Pseudo */ 71 | .highlight .vc { color: #ac4142 } /* Name.Variable.Class */ 72 | .highlight .vg { color: #ac4142 } /* Name.Variable.Global */ 73 | .highlight .vi { color: #ac4142 } /* Name.Variable.Instance */ 74 | .highlight .il { color: #d28445 } /* Literal.Number.Integer.Long */ 75 | -------------------------------------------------------------------------------- /docs/_site/css/syntax.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Name: Base16 Default Light 4 | Author: Chris Kempson (http://chriskempson.com) 5 | 6 | Pygments template by Jan T. Sott (https://github.com/idleberg) 7 | Created with Base16 Builder by Chris Kempson (https://github.com/chriskempson/base16-builder) 8 | 9 | */ 10 | .highlight .hll { background-color: #e0e0e0 } 11 | .highlight { background: #f5f5f5; color: #151515 } 12 | .highlight .c { color: #b0b0b0 } /* Comment */ 13 | .highlight .err { color: #ac4142 } /* Error */ 14 | .highlight .k { color: #aa759f } /* Keyword */ 15 | .highlight .l { color: #d28445 } /* Literal */ 16 | .highlight .n { color: #151515 } /* Name */ 17 | .highlight .o { color: #75b5aa } /* Operator */ 18 | .highlight .p { color: #151515 } /* Punctuation */ 19 | .highlight .cm { color: #b0b0b0 } /* Comment.Multiline */ 20 | .highlight .cp { color: #b0b0b0 } /* Comment.Preproc */ 21 | .highlight .c1 { color: #b0b0b0 } /* Comment.Single */ 22 | .highlight .cs { color: #b0b0b0 } /* Comment.Special */ 23 | .highlight .gd { color: #ac4142 } /* Generic.Deleted */ 24 | .highlight .ge { font-style: italic } /* Generic.Emph */ 25 | .highlight .gh { color: #151515; font-weight: bold } /* Generic.Heading */ 26 | .highlight .gi { color: #90a959 } /* Generic.Inserted */ 27 | .highlight .gp { color: #b0b0b0; font-weight: bold } /* Generic.Prompt */ 28 | .highlight .gs { font-weight: bold } /* Generic.Strong */ 29 | .highlight .gu { color: #75b5aa; font-weight: bold } /* Generic.Subheading */ 30 | .highlight .kc { color: #aa759f } /* Keyword.Constant */ 31 | .highlight .kd { color: #aa759f } /* Keyword.Declaration */ 32 | .highlight .kn { color: #75b5aa } /* Keyword.Namespace */ 33 | .highlight .kp { color: #aa759f } /* Keyword.Pseudo */ 34 | .highlight .kr { color: #aa759f } /* Keyword.Reserved */ 35 | .highlight .kt { color: #f4bf75 } /* Keyword.Type */ 36 | .highlight .ld { color: #90a959 } /* Literal.Date */ 37 | .highlight .m { color: #d28445 } /* Literal.Number */ 38 | .highlight .s { color: #90a959 } /* Literal.String */ 39 | .highlight .na { color: #6a9fb5 } /* Name.Attribute */ 40 | .highlight .nb { color: #151515 } /* Name.Builtin */ 41 | .highlight .nc { color: #f4bf75 } /* Name.Class */ 42 | .highlight .no { color: #ac4142 } /* Name.Constant */ 43 | .highlight .nd { color: #75b5aa } /* Name.Decorator */ 44 | .highlight .ni { color: #151515 } /* Name.Entity */ 45 | .highlight .ne { color: #ac4142 } /* Name.Exception */ 46 | .highlight .nf { color: #6a9fb5 } /* Name.Function */ 47 | .highlight .nl { color: #151515 } /* Name.Label */ 48 | .highlight .nn { color: #f4bf75 } /* Name.Namespace */ 49 | .highlight .nx { color: #6a9fb5 } /* Name.Other */ 50 | .highlight .py { color: #151515 } /* Name.Property */ 51 | .highlight .nt { color: #75b5aa } /* Name.Tag */ 52 | .highlight .nv { color: #ac4142 } /* Name.Variable */ 53 | .highlight .ow { color: #75b5aa } /* Operator.Word */ 54 | .highlight .w { color: #151515 } /* Text.Whitespace */ 55 | .highlight .mf { color: #d28445 } /* Literal.Number.Float */ 56 | .highlight .mh { color: #d28445 } /* Literal.Number.Hex */ 57 | .highlight .mi { color: #d28445 } /* Literal.Number.Integer */ 58 | .highlight .mo { color: #d28445 } /* Literal.Number.Oct */ 59 | .highlight .sb { color: #90a959 } /* Literal.String.Backtick */ 60 | .highlight .sc { color: #151515 } /* Literal.String.Char */ 61 | .highlight .sd { color: #b0b0b0 } /* Literal.String.Doc */ 62 | .highlight .s2 { color: #90a959 } /* Literal.String.Double */ 63 | .highlight .se { color: #d28445 } /* Literal.String.Escape */ 64 | .highlight .sh { color: #90a959 } /* Literal.String.Heredoc */ 65 | .highlight .si { color: #d28445 } /* Literal.String.Interpol */ 66 | .highlight .sx { color: #90a959 } /* Literal.String.Other */ 67 | .highlight .sr { color: #90a959 } /* Literal.String.Regex */ 68 | .highlight .s1 { color: #90a959 } /* Literal.String.Single */ 69 | .highlight .ss { color: #90a959 } /* Literal.String.Symbol */ 70 | .highlight .bp { color: #151515 } /* Name.Builtin.Pseudo */ 71 | .highlight .vc { color: #ac4142 } /* Name.Variable.Class */ 72 | .highlight .vg { color: #ac4142 } /* Name.Variable.Global */ 73 | .highlight .vi { color: #ac4142 } /* Name.Variable.Instance */ 74 | .highlight .il { color: #d28445 } /* Literal.Number.Integer.Long */ 75 | -------------------------------------------------------------------------------- /docs/guide.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: Using elfi 4 | --- 5 | 6 | # Using elfi 7 | 8 | This guide details the basic concepts of _elfi_ and its usage. 9 | 10 | For information about how to use _elfi_ in a React environment, please take a 11 | look at the [`elfi/react` docs](./react.md) 12 | 13 | ## Basic concepts 14 | 15 | _elfi_ allows you to create a _store_ which holds the whole _state_ of your 16 | application. The state is updated by dispatching functions that return a new 17 | state based on the previous ones. Such functions are called _changes_, and they 18 | should be pure functions (_i.e._ having no side effects). 19 | 20 | If you are familiar with [Flux][flux] or [Redux][redux] this might sound 21 | familiar to you, but it strives to remain simple by eliminating most of the 22 | boilerplate that you would expect to find with them. There are no dispatchers, 23 | no reducers, no actions and no action creators in _elfi_, only simple functions. 24 | 25 | Finally, the store can accept _subscribers_ which are also functions and which 26 | are called when a state change occurs. 27 | 28 | ## Creating a store 29 | 30 | Creating your _elfi_ store is done by importing `createStore` and calling it 31 | with an initial state. 32 | 33 | ```js 34 | import { createStore } from "elfi" 35 | 36 | const store = createStore(1) 37 | ``` 38 | 39 | In the example above, the state of our application is a number. This is 40 | perfectly valid and _elfi_ enforces no specific type for the internal state of 41 | the store. 42 | 43 | You can query for the current state of the store using `getState`: 44 | 45 | ```js 46 | store.getState() // => 1 47 | ``` 48 | 49 | ## Dispatching changes 50 | 51 | As mentioned previously, a _change_ is a function that returns a new state based 52 | on the current state of the store. 53 | 54 | Continuing our integer store example, we can write an `increment` change like 55 | this: 56 | 57 | ```js 58 | function increment(state) { 59 | return state + 1 60 | } 61 | ``` 62 | 63 | Such a change can be dispatched using `store.dispatch`: 64 | 65 | ```js 66 | store.dispatch(increment) 67 | store.getState() // => 2 68 | ``` 69 | 70 | Any extraneous arguments passed to dispatched will be passed to the change as 71 | well. This allows us to write and `add` change like this: 72 | 73 | ```js 74 | function add(state, n) { 75 | return state + n 76 | } 77 | 78 | store.dispatch(add, 40) 79 | store.getState() // => 42 80 | ``` 81 | 82 | ## Listening for changes 83 | 84 | You can add a subscriber by using `store.subscribe`. A subscriber is a function 85 | that takes two arguments: the old state and the new state. All subscribers of 86 | the store are called when a change occurs, and only if this change actually 87 | modifies the internal state of the store. 88 | 89 | ```js 90 | store.subscribe((oldState, newState) => console.log(newState)) 91 | store.dispatch(increment) // logs 43 92 | store.dispatch(x => x) // does not log anything since state is unchanged 93 | ``` 94 | 95 | `store.subscribe` returns a function that can be used to stop listening for 96 | changes: 97 | 98 | ```js 99 | const unsubscribe = store.subscribe(mySubscriber) 100 | // do things 101 | unsubscribe() 102 | ``` 103 | 104 | ## Middleware 105 | 106 | Middleware is a thin layer that allows you to customize the behavior of the 107 | store by hooking into the dispatching process. 108 | 109 | A middleware is a function (again!) that takes at least 3 arguments: 110 | 111 | - The next middleware function to call, 112 | - The current store state, 113 | - The change function that is being dispatched, 114 | - And any extra arguments to pass to the change. 115 | 116 | Here's an example of a simple logging middleware: 117 | 118 | ```js 119 | function loggerMiddleware(next, oldState, change, ...args) { 120 | const newState = next(oldState, change, ...args) 121 | console.log(change.name, oldState, newState) 122 | return newState 123 | } 124 | ``` 125 | 126 | You define what middleware you want to use at store creation time. `createStore` 127 | takes a second argument which is an array of middleware functions to use: 128 | 129 | ```js 130 | const store = createStore(1, [loggerMiddleware]) 131 | ``` 132 | 133 | Calling `next` chains to the next middleware piece, or to the internal 134 | dispatching mechanism. You should always return a valid state in your middleware 135 | or the internal state of your store will take the value of `undefined`. 136 | 137 | _elfi_ ships with some builtin middleware for common tasks, you can get more 138 | information about it in the [middleware documentation](./middleware.md). 139 | 140 | [flux]: https://github.com/facebook/flux 141 | [redux]: https://github.com/reactjs/redux 142 | -------------------------------------------------------------------------------- /docs/_site/react.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
25 |
26 | elfi ships with some React bindings so you can quickly start working on an
32 | application using both of them. They are available in the elfi/react module.
storeShapeThe storeShape object allows you to specify that a property or a context
37 | element of a component should behave like a store, which means it has the
38 | dispatch, getState and subscribe methods available.
import {storeShape} from "elfi/react"
41 |
42 | function Counter(props, context) {
43 | const store = props.store
44 |
45 | return <div>{store.getState()}</div>
46 | }
47 |
48 | MyComponent.propTypes = {
49 | store: storeShape.isRequired,
50 | }
51 |
52 | ProviderThe Provider is used as a container component that will trigger renders of its
57 | children every time the store is updated. It also passes the store to its
58 | children through context.
// Root.js
61 | function Root(props) {
62 | return (
63 | <Provider store={props.store}>
64 | <App />
65 | </Provider>
66 | )
67 | }
68 |
69 | // App.js
70 | function App(props, context) {
71 | // App has access to the store through context
72 | const store = context.store
73 |
74 | function increment(s) {
75 | return s + 1
76 | }
77 |
78 | function onClick() {
79 | store.dispatch(increment)
80 | }
81 |
82 | return (
83 | <div>
84 | <span>{store.getState()}</span>
85 | <button onClick={onClick}>Increment</button>
86 | </div>
87 | )
88 | }
89 |
90 | App.contextTypes = {
91 | store: storeShape.isRequired
92 | }
93 |
94 |
104 |
105 |
106 |
107 |
108 |
--------------------------------------------------------------------------------
/docs/_site/middleware.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
25 |
26 | elfi provides some builtin middleware. You’ll find documentation about them in 32 | this document.
33 | 34 |This middleware is used to log each change to whatever logging system you are 37 | using (a simple console.log by default)
38 | 39 |import {createStore} from "elfi"
40 | import createLoggerMiddleware from "elfi/middleware/logger"
41 |
42 | const logger = createLoggerMiddleware()
43 |
44 | const store = createStore(1, [logger])
45 |
46 | You can define a custom logger by passing a function to
49 | createLoggerMiddleware. This function accepts a single argument which is
50 | object with the following shape {oldState, newState, change}.
import {createStore} from "elfi"
53 | import createLoggerMiddleware from "elfi/middleware/logger"
54 |
55 | function logOldState({oldState}) {
56 | console.log(oldState)
57 | }
58 |
59 | const logger = createLoggerMiddleware(({oldState}) => console.log(oldState))
60 |
61 | const store = createStore(1, [logger])
62 |
63 | This middleware is used to add a version number to the state without triggering 68 | another state change.
69 | 70 |import {createStore} from "elfi"
71 | import createVersioningMiddleware from "elfi/middleware/versioning"
72 |
73 | const versioning = createVersioningMiddleware()
74 |
75 | const store = createStore(1, [versioning])
76 |
77 | By default, it acts as if state is a simple object and sets the version field
80 | of that object. You will probably want to define how to set the version and for
81 | this you can pass a custom setter function to createVersioningMiddleware.
It will be called with the new state as the sole argument. Here’s an example 84 | with Immutable.js:
85 | 86 |import Immutable from "immutable"
87 | import {createStore} from "elfi"
88 | import createVersioningMiddleware from "elfi/middleware/versioning"
89 |
90 | const versioning = createVersioningMiddleware(state => (
91 | state.set((state.get("version") || 0) + 1)
92 | ))
93 | const store = createStore(new Immutable.Map(), [versioning])
94 |
95 |
106 |
107 |
108 |
109 |
110 |
--------------------------------------------------------------------------------
/docs/images/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
148 |
--------------------------------------------------------------------------------
/docs/_site/images/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
148 |
--------------------------------------------------------------------------------
/docs/_site/guide.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
25 |
26 | This guide details the basic concepts of elfi and it’s usage.
32 | 33 |elfi allows you to create a store which holds the whole state of your 36 | application. The state is updated by dispatching functions that return a new 37 | state based on the previous ones. Such functions are called changes, and they 38 | should be pure functions (i.e. having no side effects).
39 | 40 |If you are familiar with Flux or Redux this might sound 41 | familiar to you, but it strives to remain simple by eliminating most of the 42 | boilerplate that you would expect to find with them. There are no dispatchers, 43 | no reducers, no actions and no action creators in elfi, only simple functions.
44 | 45 |Finally, the store can accept subscribers which are also functions and which 46 | are called when a state change occurs.
47 | 48 |Creating your elfi store is done by importing createStore and calling it
51 | with an initial state.
import {createStore} from "elfi"
54 |
55 | const store = createStore(1)
56 |
57 | In the example above, the state of our application is a number. This is 60 | perfectly valid and elfi enforces no specific type for the internal state of 61 | the store.
62 | 63 |You can query for the current state of the store using getState:
store.getState() // => 1
66 |
67 | As mentioned previously, a change is a function that returns a new state based 72 | on the current state of the store.
73 | 74 |Continuing our integer store example, we can write an increment change like
75 | this:
function increment(state) {
78 | return state + 1
79 | }
80 |
81 | Such a change can be dispatched using store.dispatch:
store.dispatch(increment)
86 | store.getState() // => 2
87 |
88 | Any extraneous arguments passed to dispatched will be passed to the change as
91 | well. This allows us to write and add change like this:
function add(state, n) {
94 | return state + n
95 | }
96 |
97 | store.dispatch(add, 40)
98 | store.getState() // => 42
99 |
100 | You can add a subscriber by using store.subscribe. A subscriber is a function
105 | that takes two arguments: the old state and the new state. All subscribers of
106 | the store are called when a change occurs, and only if this change actually
107 | modifies the internal state of the store.
store.subscribe((oldState, newState) => console.log(newState))
110 | store.dispatch(increment) // logs 43
111 | store.dispatch(x => x) // does not log anything since state is unchanged
112 |
113 | store.subscribe returns a function that can be used to stop listening for
116 | changes:
const unsubscribe = store.subscribe(mySubscriber)
119 | // do things
120 | unsubscribe()
121 |
122 | Middleware is a thin layer that allows you to customize the behavior of the 127 | store by hooking into the dispatching process.
128 | 129 |A middleware is a function (again!) that takes at least 3 arguments:
130 | 131 |Here’s an example of a simple logging middleware:
139 | 140 |function loggerMiddleware(next, oldState, change, ...args) {
141 | const newState = next(oldState, change, ...args)
142 | console.log(change.name, oldState, newState)
143 | return newState
144 | }
145 |
146 | You define what middleware you want to use at store creation time. createStore
149 | takes a second argument which is an array of middleware functions to use:
const store = createStore(1, [loggerMiddleware])
152 |
153 | Calling next chains to the next middleware piece, or to the internal
156 | dispatching mechanism. You should always return a valid state in your middleware
157 | or the internal state of your store will take the value of undefined.
elfi ships with some builtin middleware for common tasks, you can get more 160 | information about it in the middleware documentation.
161 | 162 | 163 |
171 |
172 |
173 |
174 |
175 |
--------------------------------------------------------------------------------