ts-immutable-edit
Mutate your immutable
A trifling*, super-fast library for state management. (*approx 1KiB, gzipped)
Introduction
Just like immer, this tiny library makes it easy to generate your next state by applying mutations to a proxy around your existing state.
Unlike immer, this library targets only ES6/ES2015 and above and doesn't consider anything other than plain objects and arrays (i.e. things that can be serialized to JSON and back).
Written from the ground-up in TypeScript, the package contains built-in type declarations.
Table of contents
Get started
Installation
NPM
npm install ts-immutable-edit
Yarn
yarn add ts-immutable-edit
Basic Usage
Import the edit
function:
import { edit } from "ts-immutable-edit";
Create a state:
const sourceState = { a: { b: "foo" }, c: [{ d: 1 }, { d: 2 }] };
Edit your state by mutating the draft
proxy that passed to the supplied callback:
const newState = edit(sourceState, (draft) => {
draft.c[0].d = 10;
draft.c.reverse();
});
Now, newState
is
{ a: { b: 'foo' }, c: [ { d: 2 }, { d: 10 } ] }
but sourceState
is completely untouched.
The edit
function assumes that all data passed to it is considered immutable.
As much of the source state as possible will be reused.
In the example above, all of the following statements will log true
:
// the following parts of the new object graph are newly generated
console.log(sourceState !== newState);
console.log(sourceState.c !== newState.c);
console.log(sourceState.c[0] !== newState.c[1]);
// but unchanged parts of the object graph are copied from source
console.log(sourceState.a === newState.a);
console.log(sourceState.c[1] === newState.c[0]);
Real immutability
In the examples above, although the data is treated as if it were immutable, the developer is free to mutate both the source state and the newly generated state. While this offers the fastest mode of operation, it might be desirable to work with an object graph that has been frozen via the Object.freeze
function.
deepFreeze
Included in the library is the deepFreeze
function.
import { deepFreeze } from "ts-immutable-edit";
const obj = { a: { b: "foo" }, c: [{ d: 2 }, { d: 10 }] };
deepFreeze(obj);
This function will recursively traverse the object graph, freezing every object or array that is encountered. In TypeScript, the object returned from deepFreeze
will have all of its properties marked as readonly
(this is applied recursively).
This makes it impossible to mutate any object/array in the object graph at runtime and (in the case of TypeScript) will also mean that any attempt to make modifications to a deep-frozen object will be flagged as an error in the IDE.
The deepFreeze
function does not make a copy of the object passed to it, instead freezing the object that is passed to it in-place. However, in TypeScript, the supplied object is returned with its type recursively modified such that all props are readonly
, as described above.
import { deepFreeze } from "ts-immutable-edit";
const obj = { a: { b: "foo" }, c: [{ d: 2 }, { d: 10 }] };
const readOnlyObj = deepFreeze(obj);
console.log(obj === readOnlyObj); //true
// No runtime error unless "use strict" is enabled
// but value `obj.a.b` won't change.
obj.a.b = "woo";
// TS compiler error
readOnlyObj.a.b = "woo";
Transforms
The behavior of the edit
function can be changed by providing a transform
function in the optional options
third parameter.
This library provides the freezeTransform
function, which can be supplied to the transform
option.
import { deepFreeze, freezeTransform } from "ts-immutable-edit";
const source = { a: 1, b: { a: 1 }, c: [{ a: 1 }, { a: 2 }] };
const frozenSource = deepFreeze(source);
const modified = edit(
frozenSource,
(draft) => {
draft.c[1].a = 3;
},
{
transform: freezeTransform,
}
);
When applied, the freezeTransform
will "intelligently" ensure that any modifications to the object graph are also frozen while skipping any part of the object graph that was copied directly from source. Therefore, when using the freezeTransform
, it is advisable to ensure the the original state is already recursively deep frozen (see deepFreeze section above). This will ensure that any edited object is also completely deep frozen.
transform
option
Baking-in the Instead of supplying (a 3rd) options
parameter to edit
, there exists a higher-order function, configureEdit
which allows the user to bake-in the options into the edit
function.
So, to have an edit
function which always applies the freezeTransform
, just generate your own edit
function using the configureEdit
function:
import { deepFreeze, freezeTransform, configureEdit } from "ts-immutable-edit";
const edit = configureEdit({ transform: freezeTransform });
const source = { a: 1, b: { a: 1 }, c: [{ a: 1 }, { a: 2 }] };
const frozenSource = deepFreeze(source);
const modified = edit(frozenSource, (draft) => {
draft.c[1].a = 3;
});
// The line below will cause a TypeScript error
// Under "use strict", it will also cause a run-time
// error, and without "use strict", no error, but
// no modification either.
modified.a = 2;
Example / Play
See the code above in action on