Skip to content

Commit

Permalink
Add ignoreDetached option (#58)
Browse files Browse the repository at this point in the history
  • Loading branch information
DarrenPaulWright committed Oct 30, 2020
1 parent e16328f commit 2982198
Show file tree
Hide file tree
Showing 6 changed files with 105 additions and 4 deletions.
4 changes: 4 additions & 0 deletions bench/main.bench.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ const commonBench = bench => {
object = onChange(buildObject(option.size), callback, {ignoreUnderscores: true});
}));

benchmark(`(${option.name}) ignoreDetached`, bench, buildSettings(() => {
object = onChange(buildObject(option.size), callback, {ignoreDetached: true});
}));

benchmark(`(${option.name}) empty Proxy`, bench, buildSettings(() => {
object = new Proxy(buildObject(option.size), {});
}));
Expand Down
7 changes: 7 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,13 @@ declare namespace onChange {
@default false
*/
pathAsArray?: boolean;

/**
Ignore changes to objects that become detached from the watched object.
@default false
*/
ignoreDetached?: boolean;
}
}

Expand Down
13 changes: 9 additions & 4 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ const defaultOptions = {
isShallow: false,
pathAsArray: false,
ignoreSymbols: false,
ignoreUnderscores: false
ignoreUnderscores: false,
ignoreDetached: false
};

const onChange = (object, onChange, options = {}) => {
Expand All @@ -22,12 +23,15 @@ const onChange = (object, onChange, options = {}) => {
...options
};
const proxyTarget = Symbol('ProxyTarget');
const {equals, isShallow} = options;
const {equals, isShallow, ignoreDetached} = options;
const cache = new Cache(equals);
const smartClone = new SmartClone();

const handleChangeOnTarget = (target, property, previous, value) => {
if (!ignoreProperty(cache, options, property)) {
if (
!ignoreProperty(cache, options, property) &&
!(ignoreDetached && cache.isDetached(target, object))
) {
handleChange(cache.getPath(target), property, previous, value);
}
};
Expand Down Expand Up @@ -67,7 +71,8 @@ const onChange = (object, onChange, options = {}) => {
property === 'constructor' ||
(isShallow && !SmartClone.isHandledMethod(target, property)) ||
ignoreProperty(cache, options, property) ||
cache.isGetInvariant(target, property)
cache.isGetInvariant(target, property) ||
(ignoreDetached && cache.isDetached(target, object))
) {
return value;
}
Expand Down
12 changes: 12 additions & 0 deletions lib/cache.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
'use strict';

const path = require('./path');

/**
* @class Cache
* @private
Expand Down Expand Up @@ -69,6 +71,16 @@ class Cache {
return this.isUnsubscribed ? undefined : this._pathCache.get(target);
}

isDetached(target, object) {
path.walk(this.getPath(target), key => {
if (object) {
object = object[key];
}
});

return !Object.is(target, object);
}

defineProperty(target, property, descriptor) {
if (!Reflect.defineProperty(target, property, descriptor)) {
return false;
Expand Down
9 changes: 9 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,15 @@ Default: `false`

The path will be provided as an array of keys instead of a delimited string.

##### ignoreDetached

Type: `boolean`\
Default: `false`

Ignore changes to objects that become detached from the watched object.

<br/>

### onChange.target(object)

Returns the original unwatched object.
Expand Down
64 changes: 64 additions & 0 deletions tests/on-change.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1415,3 +1415,67 @@ test('should handle changes to dates within apply trap', t => {
verify(1, proxy, '', object, [{a: clone}], 'forEach');
});
});

test('should not execute for changes on a detached object if ignoreDetached is true', t => {
const object = {
foo: {
z: {
a: 1
}
}
};

testHelper(t, object, {ignoreDetached: true}, (proxy, verify, reset) => {
proxy.foo.z.a = 2;
verify(1, proxy, 'foo.z.a', 2, 1);

const detached = proxy.foo;
const detachedNested = proxy.foo.z;

proxy.foo = {
z: 3
};
verify(2, proxy, 'foo', {z: 3}, {z: {a: 2}});

reset();

t.not(detachedNested, detached.z);
t.deepEqual(detachedNested, detached.z);
detached.z = 4;
verify(0);
});
});

test('should not execute for changes on a detached array if ignoreDetached is true', t => {
const object = {
foo: [{
z: 1
}]
};

testHelper(t, object, {ignoreDetached: true}, (proxy, verify, reset) => {
proxy.foo[0].z = 2;
verify(1, proxy, 'foo.0.z', 2, 1);

const detached = proxy.foo;
const detachedNested = proxy.foo[0];

proxy.foo = {
z: 3
};
verify(2, proxy, 'foo', {z: 3}, [{z: 2}]);

reset();

t.not(detachedNested, detached[0]);
t.deepEqual(detachedNested, detached[0]);

detached[0].z = 4;
verify(0);

detached.forEach((value, index) => {
detached[index].z++;
});
verify(0);
});
});

0 comments on commit 2982198

Please sign in to comment.