Skip to content

Instantly share code, notes, and snippets.

@rudionrails
Last active August 2, 2019 12:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rudionrails/11ac87de66e12cbd5f38129b7b54734a to your computer and use it in GitHub Desktop.
Save rudionrails/11ac87de66e12cbd5f38129b7b54734a to your computer and use it in GitHub Desktop.
Track object changes with a JavaScript proxy
const pick = (keys, object) =>
Object.entries(object).reduce(
(acc, [key, value]) => (keys.includes(key) ? {...acc, [key]: value} : acc),
{},
);
const parse = x => JSON.parse(JSON.stringify(x));
export default function proxy(initialAttrs = {}) {
let current = parse(initialAttrs);
let original = parse(initialAttrs);
const utils = {
isChanged: () =>
[...Object.keys(current), Object.keys(original)]
.filter((value, index, self) => self.indexOf(value) === index)
.some(key => current[key] !== original[key]),
applyChanged: newOriginal =>
Object.assign(original, parse(pick(Object.keys(original), newOriginal))),
};
return new Proxy(current, {
get: (obj, prop) => utils[prop] || obj[prop],
set: (obj, prop, value) => prop in obj && (obj[prop] = value),
});
}
const attrs = {foo: 'bar', bar: 'baz'};
let subject;
beforeEach(() => {
subject = proxy(attrs);
});
test('to return Object', () => {
expect(subject.constructor).toEqual(Object);
});
test('to #get value for existing key', () => {
expect(subject.foo).toEqual('bar');
expect(subject.baz).toBe(undefined);
});
test('to #set value for existing key', () => {
subject.foo = 'baz';
expect(subject.foo).toEqual('baz');
});
test('to not #set value for missing key', () => {
subject.baz = 'foo';
expect(subject.baz).toBe(undefined);
});
test('to track changed state', () => {
expect(subject.isChanged()).toBe(false);
subject.foo = 'bar';
expect(subject.isChanged()).toBe(false);
subject.foo = 'baz';
expect(subject.isChanged()).toBe(true);
});
test('to applyChanged for original state', () => {
subject.applyChanged({foo: 'bar'});
expect(subject.foo).toEqual('bar');
expect(subject.isChanged()).toBe(false);
subject.applyChanged({foo: 'xyz'});
expect(subject.foo).toEqual('bar');
expect(subject.isChanged()).toBe(true);
});
test('to not applyChanged for newly added keys', () => {
subject.applyChanged({baz: 'foo'});
expect(subject.foo).toEqual('bar');
expect(subject.bar).toBe('baz');
expect(subject.baz).toBe(undefined);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment