Skip to content

Commit

Permalink
Merge branch 'ivantm-typofixes'
Browse files Browse the repository at this point in the history
  • Loading branch information
arqex committed Jan 18, 2016
2 parents 7838c7f + a20180d commit 2b1a2fc
Showing 1 changed file with 39 additions and 36 deletions.
75 changes: 39 additions & 36 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
# Freezer

A tree data structure that is always updated from the root, even if the modification is triggered by one of the leaves, making easier to think in a reactive way.
A tree data structure that is always updated from the root, even if the modification is triggered by one of the leaves, making it easier to think in a reactive way.

[![Build Status](https://secure.travis-ci.org/arqex/freezer.svg)](https://travis-ci.org/arqex/freezer)
[![npm version](https://badge.fury.io/js/freezer-js.svg)](http://badge.fury.io/js/freezer-js)
[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/arqex/freezer?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)

Are you looking for a immutable.js alternative? Freezer is made with React.js in mind and uses real immutable structures, it is the perfect store for your Flux implementation.
Are you looking for an immutable.js alternative? Freezer is made with React.js in mind and uses real immutable structures. It is the perfect store for your Flux implementation.

What makes Freezer special is:

* Immutable trees to make fast comparison among nodes.
* Eventful nodes to notify updates to other parts of the app.
* No dependencies.
* Lightweight: ~9KB minified (much less if gzipped).
* Packaged as UMD module to be loaded everywhere.
* Packaged as UMD module that can be loaded everywhere.
* Uses common JS array and objects to store the data, so you can use it with your favourite libs like [lodash](https://lodash.com/), [underscore](http://underscorejs.org/) or [ramda](http://ramdajs.com/)

Do you want to know more?
Expand All @@ -31,6 +31,7 @@ Do you want to know more?
* [Changelog](#changelog)
* [Ask any question in the chat](https://gitter.im/arqex/freezer)


## Demos
* [You can **test** freezer.js in this JSbin](http://jsbin.com/fedeva/4/edit?js,console)
* Todo MVC using Freezer. [Code](https://github.com/arqex/freezer-todomvc) & [Demo](http://freezer-todos.divshot.io).
Expand All @@ -40,6 +41,7 @@ Do you want to know more?
* [Freezer receiving data from websockets in the Flux Challenge.](https://github.com/staltz/flux-challenge/tree/master/submissions/arqex).
* [Use freezer with redux-devtools](https://github.com/arqex/freezer-redux-devtools).


## Installation
Freezer is available as a npm package.
```
Expand All @@ -48,7 +50,8 @@ npm install freezer-js

It is possible to download the [full version](https://raw.githubusercontent.com/arqex/freezer/master/build/freezer.js) (~20KB) or [minified](https://raw.githubusercontent.com/arqex/freezer/master/build/freezer.min.js) (~9KB).

## Example of use

## Example
You can play with [this example in JSBin](http://jsbin.com/hinazasuto/edit?js,console).
```js
// Browserify/Node style of loading
Expand Down Expand Up @@ -131,30 +134,30 @@ freezer.get() === state; // true

## Why another state holder?

**Freezer** is inspired by other tree cursor libraries, specially [Cortex](https://github.com/mquan/cortex) that try to solve an inconvenient of the Flux architecture:
**Freezer** is inspired by other tree cursor libraries, specifically [Cortex](https://github.com/mquan/cortex), that try to solve an inconvenience of the Flux architecture:

* If you have a store with deep nested data and you need to update some value from a child component that reflect that data, you need to dispatch an action and from the top of the store look for the bit of data again to update it. That may involve a lot of extra code to propagate the change and it is more painful when you think that the component knew already what data to update.
* If you have a store with deep nested data and you need to update some value from a child component that reflects that data, you need to dispatch an action and from the top of the store look for the bit of data again to update it. That may involve a lot of extra code to propagate the change and it is more painful when you consider that the component already knew what data to update.

In the other hand, data changes always flowing in the same direction is what make Flux architecure so easy to reason about. If we let every component to update the data independently we are building a mess again.
On the other hand, data changes always flowing in the same direction is what makes the Flux architecure so easy to reason about. If we let every component update the data independently, we are building a mess again.

So *Freezer*, instead of letting the child component to update the data directly, gives in every node the tools to require the change. The updates are always made by the root of the store and the data can keep flowing just in one direction.
So *Freezer*, instead of letting the child component update the data directly, gives every node the tools to make the change. The updates are always made by the root of the store and the data can keep flowing in just one direction.

Imagine that we have the following tree structure as our app state: ![Initial tree](img/initialTree.png)

And we have a component responsible of handling the `state.c.f` ( the yellow node ) part of the data. Its scope is just that part of the tree, so the component receives it as a prop:
And we have a component responsible for handling the `state.c.f` ( the yellow node ) part of the data. Its scope is just that part of the tree, so the component receives it as a prop:
```js
// The component receives a part of the freezer data
this.props.branch = { h: 4, i: 5};
```
Eventually the component is used to update `state.c.f.h = 8`. You can dispatch an action with the frozen node as the payload ( making easier for your actions know what to update ), or even use the node itself to require the change:
Eventually the component is used to update `state.c.f.h = 8`. You can dispatch an action with the frozen node as the payload ( making it easier for your actions to know what to update ), or even use the node itself to make the change:
```js
this.props.branch.set( {h: 8} );
```
Then, *Freezer* will create a new immutable data structure ( a new state for your app ) starting from the top of the tree, and our component will receive a new branch to render. The state finished like this: ![Updated tree](img/updatedTree.png)
Then, *Freezer* will create a new immutable data structure ( a new state for your app ) starting from the top of the tree, and our component will receive a new branch to render. The state ends up like this: ![Updated tree](img/updatedTree.png)

Since the whole tree is updated, we can have the main app state in one single object and make the top level components re-render in a reactive way to changes that are made deep in the store hierarchy.

**Freezer** is very influenced by the way that [Facebook's Immutable](https://github.com/facebook/immutable-js) handles immutabilty. It creates a new tree every time that a modification is required referencing the non modified nodes from the previous tree. Sharing node references among frozen objects saves memory and boost the performance on creating new frozens.
**Freezer** is strongly influenced by the way that [Facebook's Immutable.js](https://github.com/facebook/immutable-js) handles immutabilty. It creates a new tree every time a modification is required, referencing the non modified nodes from the previous tree. Sharing node references among frozen objects saves memory and boosts the performance of creating new frozens.

Using immutability with React is great, because you don't need to make deep comparisons in order to know when to update a component:
```js
Expand All @@ -166,7 +169,7 @@ shouldComponentUpdate: function( nextProps ){
}
```

Instead on learning the set of methods needed to use *Immutable*, *Freezer*'s API is much simpler, it uses common JS objects and arrays to store the data, so you can start using it right now. It also makes *Freezer* much more lightweight (Minified, Immutable is ~56KB and Freezer ~9KB).
Instead of learning the set of methods needed to use *Immutable*, *Freezer*'s API is much simpler; it uses common JS objects and arrays to store the data, so you can start using it right now. It also makes *Freezer* much more lightweight (Minified, Immutable is ~56KB and Freezer ~9KB).

## API

Expand All @@ -181,16 +184,16 @@ var freezer = new Freezer({hi: 'hello'}, {mutable: true, live:true});
```
| Name | Type | Default | Description |
| ------------ | ------- | ------- | ----------- |
| **mutable** | boolean | `false` | Once you are get used to freezer, you can see that immutability is not necessary if you learn that you shouldn't update the data directly. In that case, disable immutability in case that you need a small boost in performance. |
| **live** | boolean | `false` | With live mode on, freezer triggers the update events just when the changes happen, instead of batch all the changes and trigger the event on the next tick. This is useful if you want freezer to store input field values. |
| **mutable** | boolean | `false` | Once you get used to freezer, you can see that immutability is not necessary if you learn that you shouldn't update the data directly. In that case, disable immutability in the case that you need a small performance boost. |
| **live** | boolean | `false` | With live mode on, freezer triggers the update events just when the changes happen, instead of batching all the changes and triggering the event on the next tick. This is useful if you want freezer to store input field values. |

And then, Freezer API is really simple and only have 2 methods: `get` and `set`. A freezer object also implements the [listener API](#listener-api).
And then, Freezer's API is really simple and only has 2 methods: `get` and `set`. A freezer object also implements the [listener API](#listener-api).



#### get()

Returns an frozen object with the freezer data.
Returns a frozen object with the freezer data.
```js
// Logs: {a: 'hola', b:[1,2, [3,4,5]], c: false }
console.log( freezer.get() );
Expand Down Expand Up @@ -218,11 +221,11 @@ console.log( freezer.get().c ); // false

#### Events

Every time that the data is updated, an `update` event is triggered on the freezer object. In order to use those events, *Freezer* implement the [listener API](#listener-api), and `on`, `once`, `off` and `trigger` methods are available on them.
Every time the data is updated, an `update` event is triggered on the freezer object. In order to use those events, *Freezer* implements the [listener API](#listener-api), and `on`, `once`, `off` and `trigger` methods are available on them.

## Update methods

Freezer data has three different types of nodes: *Hashes*, *Arrays* and *leaf nodes*. A leaf node can't be updated by itself and it need to be updated using its parent node. Every updating method return a new immutable object with the new node result of the update:
Freezer data has three different types of nodes: *Hashes*, *Arrays* and *leaf nodes*. A leaf node can't be updated by itself and needs to be updated using its parent node. Every updating method returns a new immutable object with the new node result of the update:
```js
var freezer = new Freezer({obj: {a:'hola', b:'adios'}, arr: [1,2]});

Expand All @@ -236,10 +239,10 @@ console.log( updatedArr ); // [0,1,2]
console.log( freezer.get() );
```

Both, *Array* and *Hashes* nodes have a `set` method to update or add elements to the node and a `reset` method to replace the node by other data.
Both *Array* and *Hash* nodes have a `set` method to update or add elements to the node and a `reset` method to replace the node with other data.

#### set( keyOrHash, value )
Arrays and hashes can update their children using the `set` method. It accepts a hash with the keys and values to update or two arguments, the key and the value.
Arrays and hashes can update their children using the `set` method. It accepts a hash with the keys and values to update or two arguments: the key and the value.
```js
var freezer = new Freezer({obj: {a:'hola', b:'adios'}, arr: [1,2]});

Expand Down Expand Up @@ -268,7 +271,7 @@ console.log( reset ); //{ foo: 'bar', bar: 'foo' }

## Util methods
#### toJS()
*Freezer* nodes are immutable. `toJS` transform *Freezer* nodes to plain mutable JS objects in case you need them.
*Freezer* nodes are immutable. `toJS` transforms *Freezer* nodes to plain mutable JS objects in case you need them.
```js
// Require node.js assert
var assert = require('assert');
Expand All @@ -290,7 +293,7 @@ var freezer = new Freezer({
}
});

// If we don't pivot, the node updated is returned
// If we don't pivot, the updated node is returned
update = freezer.get().people.John.set({age: 18});
console.log( update ); // {age: 18}

Expand All @@ -307,7 +310,7 @@ console.log( update );
The `pivot` method is really handy because when you have access to a node and update its children, it is the only way of getting the node updated to modify
other children.

The pivot is removed on the next tick, this way it won't interfere with other parts of the app.
The pivot is removed on the next tick. This way it won't interfere with other parts of the app.

#### now()
Using `now` in a node triggers the `update` method immediately.
Expand All @@ -326,9 +329,9 @@ freezer.get().set({test: 'adios'}).now();
console.log('changed');
// logs 'event' first and 'changed' after
```
Use it in cases that you need immediate updates. For example, if you are using React and you want to store an input value outside of its component, you'll need to use `now` because the user can type more than one character before the update method is triggered, losing data.
Use it in cases that you need immediate updates. For example, if you are using React and you want to store an input value outside its component, you'll need to use `now` because the user can type more than one character before the update method is triggered, losing data.

Using Freezer's [`live` option](#api) is like using now on every update.
Using Freezer's [`live` option](#api) is like using `now` on every update.

## Hash methods
#### remove( keyOrKeys )
Expand Down Expand Up @@ -371,7 +374,7 @@ freezer.get().arr
```

## Events
Freezer objects emit `update` events whenever their data changes. It is also possible to listen to `update` events in a intermediate node by creating a listener on it using the method `getListener`.
Freezer objects emit `update` events whenever their data changes. It is also possible to listen to `update` events in an intermediate node by creating a listener on it using the `getListener` method.

#### getListener()
Returns a listener that emits an `update` event when the node is updated. The listener implements the [listener API](#listener-api).
Expand All @@ -390,7 +393,7 @@ state.arr.push( 3 ); //logs 'Updated!' [2,3]
```

## Listener API
Freezer instances and listeners implement this API that is influenced by the way Backbone handle events. The main event that Freezer emits is `update`, and it is emitted on every node update.
Freezer instances and listeners implement an API influenced by the way Backbone handles events. The main event that Freezer emits is `update`, and it is emitted on every node update.

#### on( eventName, callback )
Register a function to be called when an event occurs.
Expand All @@ -415,9 +418,9 @@ Store.trigger('add', 4, 5); // Will log 'add', 4, 5
This is a nice way of binding [reactions](#usage-with-react) to more than one type of event.

## Batch updates
At some point you will find yourself wanting to apply multiple changes on some node at a time. On every change the full tree is re-generated, but probably the only tree needed is the final result of all those changes.
At some point you will find yourself wanting to apply multiple changes at a time. The full tree is re-generated on each change, but the only tree you probably need is the final result of all those changes.

Freezer nodes offer a `transact` method to make local modifications to them without generating intermediate frozen trees, and a `run` method to commit all the changes at once. This way your app can have a really good performance.
Freezer nodes offer a `transact` method to make local modifications to them without generating intermediate frozen trees, and a `run` method to commit all the changes at once. This way your app can have really good performance.

```js
var freezer = new Freezer({list:[]}),
Expand Down Expand Up @@ -447,10 +450,10 @@ freezer.get().list; // [1000, 1, 2, ..., 999]

Transactions are designed to always commit the changes, so if you start a transaction but you forget to call `run`, it will be called automatically on the next tick.

It is possible to update children nodes of a node that is making a transaction, but it is not really recommended. Those updates will not update the store until the transaction in the parent node is commited, and that may lead to confussion if you use child nodes as common freezer nodes. Updating child nodes doesn't improve its performance much because of having a transacting parent, so it is recommended to make the changes in the transaction node and run it as soon as you have finished with the modifications to prevent undesired behavior.
It is possible to update the child nodes of a node that is making a transaction, but it is not really recommended. Those updates will not update the store until the transaction in the parent node is commited, and that may lead to confusion if you use child nodes as common freezer nodes. Updating child nodes doesn't improve the performance much because they have a transacting parent, so it is recommended to make the changes in the transaction node and run it as soon as you have finished with the modifications to prevent undesired behavior.

## Usage with React
Creating data driven React applications using Freezer is really simple. Just wrap your top React component in order to pass the app state as a prop. Then, re-render on any state change.
Creating data-driven React applications using Freezer is really simple. Just wrap your top React component in order to pass the app state as a prop. Then, re-render on any state change.

```js
var AppContainer = React.createClass({
Expand All @@ -465,7 +468,7 @@ var AppContainer = React.createClass({
});
```

Freezer can be used along with any Flux library, but it is also possible to use it in a Flux alike way without any framework.
Freezer can be used along with any Flux library, but it is also possible to use it in a Flux-like way without any framework.

Instead of calling actions we can trigger custom events, thanks to the open event system built in Freezer. Those events accept any number of parameters.

Expand All @@ -482,11 +485,11 @@ State.on('products:addToCart', function (product, cart) {
});
```

Listener methods that update the state are called **reactions**, ( we are building reactive applications, are't we?). It is nice to organize them in files by their domain, like if they were flux stores, but with the difference that all the domains store the data in the same Freezer object.
Listener methods that update the state are called **reactions**, ( we are building reactive applications, are't we? ). It is nice to organize them in files by their domain, as though they were flux stores, but with the difference that all the domains store the data in the same Freezer object.

If you need to coordinate state updates, you can trigger new events when a reaction finishes, or listen to specific nodes, no need of `waitFor`.
If you need to coordinate state updates, you can trigger new events when a reaction finishes, or listen to specific nodes, without the need for `waitFor`.

This is all what it takes to understand Flux apps with Freezer. No complex concepts like observers, reducers, payloads or action creators. Just events and almost no boilerplate code.
This is all it takes to understand Flux apps with Freezer. No complex concepts like observers, reducers, payloads or action creators. Just events and almost no boilerplate code.

You can check this approach working in the [TodoMVC sample app](https://github.com/arqex/freezer-todomvc), or in the [flux comparison project](https://github.com/voronianski/flux-comparison).

Expand Down

0 comments on commit 2b1a2fc

Please sign in to comment.