Freezer
A tree data structure that emits events on updates, even if the modification is triggered by one of the leaves, making it easier to think in a reactive way.
Install / Use
/learn @arqex/FreezerREADME
Freezer
A tree data structure that emits events on updates, even if the modification is emited by one of the leaves, making it easier to think in a reactive way.
Are you looking for an immutable.js alternative? Freezer is made with React.js in mind and it uses real immutable structures. It is the perfect store for you application.
Are you looking for a Redux alternative? Using Freezer you don't even need a flux framework, just listen to its update events to refresh your UI. Goodbye boilerplate code!.
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 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, underscore or ramda
Do you want to know more?
- Demos
- Installation
- Example
- Motivation
- Freezer API
- Updating the data
- Events
- Batch updates
- Usage with React
- Changelog
- Ask any question in the chat
Demos
- You can test freezer.js in this JSbin
- Todo MVC using Freezer. Code & Demo.
- How to use React and Freezer together.
- A JSON editor with undo and redo, and here the blog article explaining it .
- The flux comparison project.
- Freezer receiving data from websockets in the Flux Challenge.
- Use freezer with redux-devtools.
Installation
Freezer is available as a npm package.
npm install freezer-js
It is possible to download a file to use it directly in the browser. Grab the full version (~20KB) or minified one (~9KB).
Example
You can play with this example in JSBin.
// Browserify/Node style of loading
var Freezer = require('freezer-js');
// Let's create a freezer object
var freezer = new Freezer({
a: {x: 1, y: 2, z: [0, 1, 2] },
b: [ 5, 6, 7 , { m: 1, n: 2 } ],
c: 'Hola',
d: null // It is possible to store whatever
});
// Let's get the frozen data stored
var state = freezer.get();
// Listen to changes in the state
freezer.on('update', function( currentState, prevState ){
// currentState will have the new state for your app
// prevState contains the old state, in case you need
// to do some comparisons
console.log( 'I was updated' );
});
// The data is read as usual
console.log( state.c ); // logs 'Hola'
// And used as usual
state.a.z.forEach( function( item ){
console.log( item );
}); // logs 0, 1 and 2
// But it is immutable, so...
state.d = 3; console.log( state.d ); // logs null
state.e = 4; console.log( state.e ); // logs undefined
// to update, use methods like set that returns new frozen data
var updated = state.set( 'e', 4 ); // On next tick it will log 'I was updated'
console.log( state.e ); // Still logs undefined
console.log( updated.e ); // logs 4
// freezer's data has changed!
freezer.get() !== state; // true
freezer.get() === updated; // true
// The nodes that weren't updated are reused
state.a === updated.a; // true
state.b === updated.b; // true
// Updates can be chained because the new immutable
// data node is always returned
var updatedB = updated.b
.push( 50 )
.push( 100 )
.shift()
.set(0, 'Updated')
; // It will log 'I was updated' on next tick, just once
// updatedB is the current b property
freezer.get().b === updatedB; // true
// And it is different from the one that started
updated !== freezer.get(); // true
updated.b !== updatedB; // true
console.log( updated.b[0] ); // updated did't/can't change: logs 5
console.log( updatedB[0] ); // logs 'Updated'
console.log( updatedB[4] ); // logs 100
updatedB.length === 5; // true: We added 2 elements and removed 1
// Untouched nodes are still the same
state.a === freezer.get().a; // still true
updated.a === freezer.get().a; // still true
// Reverting to a previous state is as easy as
// set the data again (Undo/redo made easy)
freezer.set( state ); // It will log 'I was updated' on next tick
freezer.get() === state; // true
Why another state holder?
Freezer is inspired by other tree cursor libraries, specifically 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 reflects that data, you need to dispatch an action that needs to 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.
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 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:

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:
// 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 it easier for your actions to know what to update ), or even use the node itself to make the change:
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 ends up like this: 
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 strongly influenced by the way that Facebook's 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:
shouldComponentUpdate: function( nextProps ){
// The comparison is fast, and we won't render the component if
// it does not need it. This is a huge gain in performance.
return this.props.prop != nextProps.prop;
}
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
Create a freezer object using the constructor:
var freezer = new Freezer({a: 'hola', b:[1,2, [3,4,5]], c: false });
Freezer can be initialized with an object or an array:
var arrayStore = new Freezer( [1, 2, {foo: 'bar'}] );
A freezer object can accept options on initialization:
var freezer = new Freezer({hi: 'hello'}, {mutable: true, live:true});
| Name | Type | Default | Description |
| ------------ | ------- | ------- | ----------- |
| 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 emits the update events just when the changes happen, instead of batching all the changes and emiting the event on the next tick. This is useful if you want freezer to store input field values. |
| freezeInstances | boolean | false | It's possible to store class instances in freezer. They are handled like strings or numbers, added to the state like non-frozen leaves. Keep in mind that if their internal state changes, freezer won't emit any update event. If you want freezer to handle them as freezer nodes, set 'freezerInstances: tr
Related Skills
bluebubbles
330.7kUse when you need to send or manage iMessages via BlueBubbles (recommended iMessage integration). Calls go through the generic message tool with channel="bluebubbles".
node-connect
330.7kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
slack
330.7kUse when you need to control Slack from OpenClaw via the slack tool, including reacting to messages or pinning/unpinning items in Slack channels or DMs.
frontend-design
81.4kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
