haaku
v0.2.2
Published
immutable state management
Downloads
19
Maintainers
Readme
haaku
Immutable state update utilities.
npm install haaku
Usage
Given an object:
const people = {
kevin: { age: 29, pets: ['maggie'], trash: 'removeme', },
rafael: { age: 31, pets: ['flitch', 'haku'] }
};
We can use from
or merge
to produce a new object whilst leaving the original object intact. Both examples below are practically equivalent.
import { from } from 'haaku';
const updated = from(people, draft => {
draft.kevin.age = 30;
draft.kevin.pets.push('trixie');
delete draft.kevin.trash;
});
or
import { merge } from 'haaku';
const updated = merge(people, {
kevin: {
age: 30,
// use function patches to update a property based on its previous value
pets: prev => [...prev, 'trixie'],
// set a property to `undefined` to remove it from the object
trash: undefined
}
});
Both examples above will produce the given object:
// new object is created
console.log(people === updated); // false
// original object remains unmutated
console.log(people.kevin.age); // 29
console.log(people.kevin.pets); // ['maggie']
console.log('trash' in people.kevin); // true
// unchanged references are "shared"
console.log(people.rafael === updated.rafael); // true
// changed objects & arrays will recursively be cloned
console.log(people.kevin === updated.kevin) // false
console.log(updated.kevin.age); // 30
console.log(updated.kevin.pets); // ['maggie', 'trixie']
console.log('trash' in updated.kevin); // false
API
from(obj: Object, (draft: Proxy) => undefined): Object
from
accepts two arguments:
- An object that will be used as the base; this object will remain unmutated
- A recipe function to produce a new object
merge(obj: Object, ...patches: Object[]): Object
merge
accepts two or more arguments:
- An object that will be used as the base; this object will remain unmutated
- Any number of object patches; each patch will recursively be merged with the base state
Caveats
merge
will not deep merge nested arrays:
const a = { nums: [1, 2, 3] };
const b = merge(a, { nums: [4, 5, 6] });
console.log(b); // { nums: [4, 5, 6] }
In cases where you'd like to make changes to an existing array, you can use the spread operator in conjunction with function patches:
const a = { nums: [1, 2, 3] };
const b = merge(a, { nums: prev => [...prev, 4, 5, 6] });
console.log(b); // { nums: [1, 2, 3, 4, 5, 6] }
If you require more robust functionality for merging arrays, I recommend deepmerge and its arrayMerge
utilities.
Note that function patches will be passed original references. Mutating these will mutate the original object:
const a = { nums: [1, 2, 3] };
const b = merge(a, {
nums: prev => {
prev.push(4); // don't do this
return prev;
}
});
console.log(a); // { nums: [1, 2, 3, 4] } The original object has been mutated! :(
console.log(b); // { nums: [1, 2, 3, 4] }
In cases where you'd like the benefits of mutable objects without mutating the base object, you can use from
in conjunction with merge
:
import { from, merge } from 'haaku';
const a = { nums: [1, 2, 3] };
const b = merge(a, {
nums: prev => from(prev, draft => {
draft.push(4);
})
});
console.log(a); // { nums: [1, 2, 3] } The original object is not mutated :)
console.log(b); // { nums: [1, 2, 3, 4] }
Credits
Thank you pygy for his wisdom in fixing a major bug. Inspired by clean-set, Immer, and mergerino.