Skip to content

Commit

Permalink
Merge pull request #10 from evanrs/next
Browse files Browse the repository at this point in the history
Add cursors, coerce namespace and keys to property paths
  • Loading branch information
Evan Schneider committed Mar 9, 2016
2 parents 9ef805a + 639c076 commit 93b8cc7
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 31 deletions.
65 changes: 46 additions & 19 deletions src/next/connect.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { Component, PropTypes, createElement } from 'react'
import hoistStatics from 'hoist-non-react-statics'
import invariant from 'invariant'
import result from 'lodash/result';
import toPath from 'lodash/toPath';
import isFunction from 'lodash/isFunction';
import isString from 'lodash/isString';
import memoize from 'lodash/memoize';

import { create } from './create'

Expand All @@ -12,39 +15,65 @@ const storeShape = PropTypes.shape({
getState: PropTypes.func.isRequired
});

export function connect(namespace) {

const connectNamespace = memoize(create);


export function connect(namespace, reducer) {
invariant(isString(namespace) || isFunction(namespace),
`Expected "namespace" to be of type string or function`
);

return function wrapWithComponent (WrappedComponent) {
class Connect extends Component {
constructor(props, context) {
super(props, context)
super(...arguments);

this.store = props.store || context.store

invariant(this.store,
`Could not find "store" in either the context or ` +
`props of "${this.constructor.displayName}". `
)

this.childProps = create(namespace, this.store)
this.namespace = this.getNamespace(props, this.store)
this.state = {
namespace: result(this.store.getState(), `namespace.${namespace}`, {}),
version: 0
version: this.namespace.version()
}
}

getNamespace(props=this.props, store=this.store) {
return (
connectNamespace(
[ 'namespace',
...toPath(
isFunction(namespace) ?
namespace(state, props) : namespace) ],
store
)
)
}

componentDidMount() {
this.unsubscribe = this.store.subscribe(this.handleChange.bind(this))
if (! this.unsubscribe) {
this.unsubscribe =
this.store.subscribe(this.handleChange.bind(this));
this.handleChange();
}
}

componentWillUnmount() {
this.unsubscribe()
this.unsubscribe = null
if (this.unsubscribe) {
// comma operator, because why not?
this.unsubscribe = this.unsubscribe(), null;
}
}

componentWillUpdate(nextProps, nextState) {
if (nextState.version !== this.state.version) {
this.childProps = {
...this.childProps,
version: nextState.version
this.namespace = {
...this.getNamespace(nextProps),
_version: nextState.version
}
}
}
Expand All @@ -54,19 +83,17 @@ export function connect(namespace) {
return
}

const prev = this.state.namespace
const next = result(this.store.getState(), `namespace.${namespace}`, prev)
const prev = this.state.version
const next = this.namespace.version();


if (prev !== next) {
this.setState({
namespace: next,
version: this.state.version + 1
})
this.setState({ version: next })
}
}

render() {
return createElement(WrappedComponent, { ...this.props, [namespace]: this.childProps })
return createElement(WrappedComponent, { ...this.props, [namespace]: this.namespace })
}
}

Expand Down
41 changes: 35 additions & 6 deletions src/next/create.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import flow from 'lodash/flow';
import get from 'lodash/get';
import isFunction from 'lodash/isFunction';
import isNil from 'lodash/isNil';
import isObject from 'lodash/isObject';
import isString from 'lodash/isString';
import result from 'lodash/result';
import flow from 'lodash/flow';
import property from 'lodash/property';
import mapValues from 'lodash/mapValues';
import property from 'lodash/property';
import result from 'lodash/result';
import toPath from 'lodash/toPath';

import invariant from 'invariant';

import { BIND } from './reducer';

Expand All @@ -23,8 +29,12 @@ export function assign(namespace, key, value) {

export function create(namespace, store) {
const { dispatch, getState } = store;

const selectNamespace =
property(['namespace', ...toPath(namespace)])

const getNamespace =
flow(getState, property(['namespace', namespace]));
flow(getState, selectNamespace);

function selector(key, __) {
return arguments.length > 0 ?
Expand All @@ -38,6 +48,7 @@ export function create(namespace, store) {
// curry assign with target
isString(target) ?
dispatcher.bind(this, target)
// TODO interpret array as property.path
// map target ({key: value}) => assign
: mapValues(target, (value, key) => dispatcher(key, value))
// deferred selector
Expand All @@ -51,7 +62,7 @@ export function create(namespace, store) {
)
}

return {
const ns = {
assign: dispatcher,
assigns(key, selector) {
return dispatcher(key, (value, ...args) =>
Expand All @@ -60,13 +71,31 @@ export function create(namespace, store) {
: value
)
},
cursor(path, defaultValue={}) {
let nspath = toPath([toPath(namespace), toPath(path)])
let cursor = create(nspath, store);

return cursor;
},
dispatch,
select: selector,
selects() {
return selector.bind(null, ...arguments);
},
touched(key) {
return selector(['@@touched'].concat(key), false);
return selector(['@@touched', ...toPath(key)], false);
},
reset(key) {
dispatcher(key, null);
dispatcher(['@@touched', ...toPath(key)], null);
},
resets(key) {
return ns.reset.bind(ns, key);
},
version() {
return selector('@@version', 0)
}
}

return ns;
}
44 changes: 38 additions & 6 deletions src/next/reducer.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,51 @@
import result from 'lodash/result';
import concat from 'lodash/concat';
import get from 'lodash/get';
import merge from 'lodash/merge';
import set from 'lodash/set';
import toPath from 'lodash/toPath';
import clone from 'lodash/clone';


export const BIND = 'BIND_NAMESPACE_NEXT';


export function namespaceReducer (state={}, action={}) {

if (action.type === BIND) {
let { payload: { namespace, key, value } } = action

let prev = result(state, namespace, {});
let touched = result(prev, '@@touched', {});
let next = {
...prev, [key]: value, ['@@touched']: { ...touched, [key]: true } };
namespace = toPath(namespace);
key = toPath(key);

let changedPath = concat(namespace, key);
let touchedPath = concat(namespace, '@@touched', key);
let versionPath = concat(namespace, '@@version');

if (value !== get(state, changedPath)) {
let version = get(state, versionPath, 0) + 1;
let fragment = set({}, namespace, get(state, namespace));

clonePath(fragment, changedPath);
clonePath(fragment, touchedPath);

set(fragment, versionPath, version);
set(fragment, changedPath, value);
set(fragment, touchedPath, true);

state = { ...state, [namespace]: next };
state = merge(clone(state), fragment)
}
}


return state;
}


function clonePath (target, path) {
path.forEach((key, idx, col) => {
key = col.slice(0, idx);
set(target, key, clone(get(target, key)))
})

return target;
}

0 comments on commit 93b8cc7

Please sign in to comment.