Skip to content

Instantly share code, notes, and snippets.

@novwhisky
Last active October 12, 2017 20:56
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 novwhisky/2df539f25c1bb7c57b7f9e6c1b8e9844 to your computer and use it in GitHub Desktop.
Save novwhisky/2df539f25c1bb7c57b7f9e6c1b8e9844 to your computer and use it in GitHub Desktop.
Rewind selectors

rewindSelector.js

Uses ES2015 Proxy to spy on selector property accesses and returns an object describing the expected state tree

Does not work with IE, even with proxy-polyfill

Usage

Given a set of basic selectors

const A = state => state.a;
const B = state => state.b;
const C = state => state.c;

I can compose a single selector that does the equivalent of reading state.a.b.c like so

const compositeSelector = state => C(B(A(state)));

It's entirely possible for composite selectors to be generated at run-time, meaning our code may not have the information necessary to know what state shape a given selector depends on. With the use of Proxy, we're able to spy on every property a selector tries to access and derive the state tree it is expecting

const stateTree = rewindSelector(compositeSelector);

console.log(stateTree);
>>> {
  "a": {
    "b": {
      "c": {}
    }
  }
}

Selectors using deep property acess work as well

const A = state => state.a;
const B = state => state.b[1][2];
const C = state => state.c;
const compositeSelector = state => (C(B(A(state)));

const stateTree = rewindSelector(compositeSelector);

console.log(stateTree);
>>> {
  "a": {
    "b": {
      "1": {
        "2": {
          "c": {}
        }
      }
    }
  }
}
function shiftKeyPath(keyPath) {
const [key, ...path] = keyPath.split('.');
return [key, path.join('.')];
}
function scaffold(keyPath, state={}, targetValue={}) {
const stateCopy = Object.assign({}, state);
const [key, path] = shiftKeyPath(keyPath);
const ctx = stateCopy[key];
if(path.length > 0) {
stateCopy[key] = scaffold(path, ctx, targetValue);
}
else {
stateCopy[key] = targetValue;
}
return stateCopy;
}
function recursiveProxy(callback) {
// No support in IE (even with proxy-polyfill)
return new Proxy({}, {
get(target, prop) {
callback(prop);
return recursiveProxy(callback);
}
});
}
/**
* Uses ES2015 Proxy to spy on selector property accesses and returns an object
* describing the expected state tree
* @param {Function} selector
* @returns {Object}
*/
export function rewindSelector(selector) {
let scaf = {};
const keyPaths = [];
const p = recursiveProxy(
prop => {
keyPaths.push(prop);
scaf = scaffold(keyPaths.join('.'))
});
selector(p);
return scaf;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment