reupdate v1.12.0
Selectors & React.PureComponent friendly immutable update
reupdate can help you to:
- Reduce recalculations in existing
reselectselectors used redux state: just create reducers withreupdate - Even more reduce recalculations with
reupdateselectors (with same API asreselect) - Reduce re-rendering of your
reactcomponents
Examples
- reupdate vs reselect: avoid extra recalculations (working test!)
redux + reupdate: avoid extra state changes (working test!)
Also includes comparation tests for:
Note
reupdate has no dependencies to redux and react, etc. so you can use it with other frameworks.
Contents
- Motivation
- Design Rule
- Difference from
object-path-immutableandimmutability-helper - Install
- Quick usage
- Imports
- API
- Tests
- Credits
Motivation
Typically, the state changes less frequently than it is read.
To avoid extra re-evaluations (and re-rendering) with redux, reselect, react and others we should return same reference to the (maybe nested) state when it was updated but actually not changed (some isDeepEqual(state, nextState) gives true).
Design rule
If your update of src value do not change it (in sense of deep equality) then result === src must give true:
isDeepEqual(src, value) |=> result === src
This rule also must work for nested not changed values as is:
isDeepEqual(src.a.b.c, value.a.b.c) |=> result.a.b.c === src.a.b.c
Difference from object-path-immutable and immutability-helper
object-path-immutable and immutability-helper usually expect that you know what is the difference from src and value and some times returns reference for value despite it is deep equal to src. As a result we have extra recalculations of selectors and/or re-rendering of components.
In such cases reupdate returns reference to src, so it prevents extra recalculations and re-rendering. Profit! See examples!
Install
yarn add reupdateor
npm i reupdateQuick usage
import {set, setAt, extend} from 'reupdate';
import cloneDeep from 'lodash/cloneDeep';
const src = {
a: {
b: {
c: 'c',
d: 'd'
},
e: 'e',
f: [
{f0: 'f0'},
{f1: 'f1'},
{f2: 'f2'}
]
},
info: {
name: 'smth',
coord: {x: 1, y: 2}
}
}
let res;
res = set(src, src); // res === src
res = set(src, cloneDeep(src)); // res === src
res = setAt(src, 'a.f[1].f1', 123);
// res.a !== src.a
// res.a.f !== src.a.f
// res.a.f[1].f1 !== src.a.f[1].f1
/** But following references were saved!: */
// res.a.b === src.a.b
// res.a.e === src.a.e
// res.a.f[0] === src.a.f[0]
// res.a.f[2] === src.a.f[2]
res = extend(src, {
info: {
name: 'New Name',
coord: {x: 1, y: 2} // coord not actually changed
}
});
// res.info !== src.info
// res.info.name !== src.info.name
/** But following references were saved!: */
// res.a === src.a
// res.info.coord === src.info.coord Imports
You can import functions like this:
import {set, insert, createSelector} from 'reupdate';Or like this (to reduce bundle size with webpack or other bundlers):
import set from 'reupdate/set';
import insert from 'reupdate/insert';
import createSelector from 'reupdate/createSelector';API
- Generic functions
- Object spcific functions
- Array specific functions
- push(srcArray, ...values)
- pushAt(src, pathToArray, ...values)
- pop(srcArray, n = 1)
- popAt(src, pathToArray, n = 1)
- insert(srcArray, atIndex, ...values)
- insertAt(src, pathToArray, atIndex, ...values)
- splice(srcArray, atIndex, deleteCount, ...values)
- spliceAt(src, pathToArray, atIndex, deleteCount, ...values)
- shift(srcArray, n = 1)
- shiftAt(src, pathToArray, n = 1)
- unshift(srcArray, ...values)
- unshiftAt(src, pathToArray, ...values)
- Selectors related
set(value, newValue)
Returns value if newValue has nothing new (is deep equal to value).
Returns new object/array with:
- Same references to not changed properties/items
- New references to changed properties/items
Example:
import set from 'reupdate/set'
const src = {
name: 'Alex',
info: {
greeting: 'Hello',
description: 'I am developer'
},
address: {
country: 'Russia',
city: 'Moscow'
},
friends: [
{ name: 'Vasya', age: 25 },
{ name: 'Fedor', age: 33 }
]
};
const replacement = {
name: 'Alex',
info: {
greeting: 'Hello',
description: 'I am developer'
},
address: {
country: 'Russia',
city: 'Moscow'
},
friends: [
{ name: 'Vasya', age: 3 }, // The ONLY actual change!
{ name: 'Fedor', age: 33 }
]
};
const res = set(src, replacement);
expect(res).toEqual(replacement); // Correct result
expect(res === src).toBe(false);
expect(res.info === src.info).toBe(true); // Same reference!
expect(res.address === src.address).toBe(true); // Same reference!
expect(res.friends === src.friends).toBe(false);
expect(res.friends[0] === src.friends[0]).toBe(false);
expect(res.friends[1] === src.friends[1]).toBe(true); // Same reference!setAt(value, path, newValue)
set nested part of value
updateAt(value, path, updater)
Example:
updateAt(state, 'a.b[1].c', c => c + 1)deleteAt(value, path)
Equal to setAt(value, path, undefined)
Important edge case: delete undefined value saves reference:
import deleteAt from 'reupdate/deleteAt';
const value = {x: 1};
const res = deleteAt(value, 'y');
// value === resextend(src, extensionOrCreator)
Params:
- src: source
Object - extensionOrCreator:
Object|Object => Object- Extension object
- Function for creating extension object from
src
Returns src if extensionOrCreator has nothing new (has deep equal only properties).
Returns new object with:
- Same references to not changed properties
New references to changed properties
Important edge case: extend with empty extensionOrCreator saves reference: src === extend(src, {})
import extend from 'reupdate/extend';
const src = {
name: 'Alex',
info: {
greeting: 'Hello',
description: 'I am developer'
},
address: {
country: 'Russia',
city: 'Moscow'
},
friends: [
{ name: 'Vasya', age: 25 },
{ name: 'Fedor', age: 33 }
]
};
const extension = {
friends: [
{ name: 'Vasya', age: 3 }, // The ONLY actual change!
{ name: 'Fedor', age: 33 }
]
};
const res = extend(src, extension);
expect(res).toEqual({...src, ...extension}); // Correct result
expect(res === src).toBe(false);
expect(res.info === src.info).toBe(true); // Same reference!
expect(res.address === src.address).toBe(true); // Same reference!
expect(res.friends === src.friends).toBe(false);
expect(res.friends[0] === src.friends[0]).toBe(false);
expect(res.friends[1] === src.friends[1]).toBe(true); // Same reference!extendAt(src, pathToObject, extensionOrCreator)
extend nested object of value
Params:
- pathToObject:
string- lodash path string - See
extend
push(srcArray, ...values)
Important edge case: push empty values saves reference: srcArray === push(srcArray)
pushAt(src, pathToArray, ...values)
Example:
const state = {a: {b: [{x: 1}, {y: 2}]}}
const newState = pushAt(state, 'a.b', {z:3}, {w: 4})
// newState = {a: {b: [{x: 1}, {y: 2}, {z:3}, {w: 4}]}}Important edge case: pushAt empty values saves reference: src === pushAt(src, 'a.b')
pop(srcArray, n = 1)
Important edge case: pop 0 items saves reference: srcArray === pop(srcArray, 0)
popAt(src, pathToArray, n = 1)
Important edge case: pop 0 items saves reference: src === popAt(src, 'a.b', 0)
insert(srcArray, atIndex, ...values)
Important edge case: insert empty values at any index saves reference: srcArray === insert(srcArray, i)
insertAt(src, pathToArray, atIndex, ...values)
Important edge case: insert empty values at any index saves reference: src === insert(src, 'a.b', i)
splice(srcArray, atIndex, deleteCount, ...values)
Important edge cases:
- Delete 0 and insert empty
valuessaves reference:srcArray === splice(srcArray, i, 0) - Delete and insert the same
valuessaves reference:import splice from 'reupdate/splice'; const srcArray = [a, b, c, d]; const atIndex = 1; const res = splice(srcArray, atIndex, 2, b, c); // srcArray === res;
spliceAt(src, pathToArray, atIndex, deleteCount, ...values)
Important edge cases:
- Delete 0 and insert empty
valuessaves reference:src === spliceAt(src, 'a.b', i, 0) - Delete and insert the same
valuessaves reference (seespliceexample)
shift(srcArray, n = 1)
Important edge case: shift 0 items saves reference: srcArray === shift(srcArray, 0)
shiftAt(src, pathToArray, n = 1)
Important edge case: shift 0 items saves reference: src === shift(src, 'a.b', 0)
unshift(srcArray, ...values)
Important edge case: unshift empty values saves reference: srcArray === unshift(srcArray)
unshiftAt(src, pathToArray, ...values)
Example:
const state = {a: {b: [{x: 1}, {y: 2}]}}
const newState = pushAt(state, 'a.b', {z:3}, {w: 4})
// newState = {a: {b: [{z:3}, {w: 4}, {x: 1}, {y: 2}]}}Important edge case: unshiftAt empty values saves reference: src === unshiftAt(src, 'a.b')
createSelector(...inputSelectors | inputSelectors, resultFunc)
Wrapper for reselect.createSelector
createStructuredSelector({inputSelectors}, selectorCreator = createSelector)
Wrapper for reselect.createStructuredSelector
Tests
Run yarn test index to check that reupdate saves references.
Run yarn test object-path-immutable to check that object-path-immutable NOT saves references (some tests will fail).
Run yarn test immutability-helper to check that immutability-helper NOT saves references (some tests will fail).
Credits
- Alexander Nuikin - Author
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago