Skip to content

Instantly share code, notes, and snippets.

@novwhisky
Last active Oct 12, 2017
Embed
What would you like to do?
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