Created
March 20, 2016 17:47
-
-
Save abhiaiyer91/fb558dde972db140bccb to your computer and use it in GitHub Desktop.
Workpop State Container using TrackerReact
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function composeStateContainer(subscriptions, reactiveFn) { | |
return function wrappedComposeContainer(WrappedComponent) { | |
class ComposedContainer extends React.Component { | |
constructor() { | |
super(); | |
this.state = {}; | |
this._subscriptionHandles = {}; | |
// This dependency is used to identify state transitions in | |
// _subscriptionHandles which could cause the result of | |
// Basically this is triggered whenever a new subscription | |
// handle is added or when a subscription handle | |
// is removed and they are not ready. | |
this._allSubsReadyDep = new Tracker.Dependency(); | |
this._allSubsReady = false; | |
this.subHandle = {}; | |
this.subscriptionImplementation = this.subscriptionImplementation.bind(this); | |
this.subscribe = this.subscribe.bind(this); | |
this.subscriptionsReady = this.subscriptionsReady.bind(this); | |
this.reactiveState = this.reactiveState.bind(this); | |
} | |
componentWillMount() { | |
subscriptions(this.subscribe); | |
} | |
componentWillUnmount() { | |
this.subHandle.stop(); | |
} | |
subscriptionsReady() { | |
this._allSubsReadyDep.depend(); | |
this._allSubsReady = _.all(this._subscriptionHandles, function (handle) { | |
return handle.ready(); | |
}); | |
return this._allSubsReady; | |
} | |
subscribe(/* arguments */) { | |
let subHandle; | |
const subHandles = this._subscriptionHandles; | |
const args = _.toArray(arguments); | |
let options = {}; | |
if (_.size(args)) { | |
const lastParam = _.last(args); | |
// Match pattern to check if the last arg is an options argument | |
const lastParamOptionsPattern = { | |
onReady: Match.Optional(Function), | |
onStop: Match.Optional(Function), | |
connection: Match.Optional(Match.Any) | |
}; | |
if (_.isFunction(lastParam)) { | |
options.onReady = args.pop(); | |
} else if (lastParam && !_.isEmpty(lastParam) && Match.test(lastParam, lastParamOptionsPattern)) { | |
options = args.pop(); | |
} | |
options.onStop = () => { | |
// When the subscription is stopped, remove it from the set of tracked | |
// subscriptions to avoid this list growing without bound | |
delete subHandles[subHandle.subscriptionId]; | |
// Removing a subscription can only change the result of subscriptionsReady | |
// if we are not ready (that subscription could be the one blocking us being | |
// ready). | |
if (!this._allSubsReady) { | |
this._allSubsReadyDep.changed(); | |
} | |
}; | |
} | |
const connection = options.connection; | |
const callbacks = _.pick(options, ["onReady", "onError", "onStop"]); | |
// The callbacks are passed as the last item in the arguments array passed to subscribe | |
args.push(callbacks); | |
// subscribe takes the connection as one of the options in the last argument | |
subHandle = this.subscriptionImplementation(args, { | |
connection | |
}); | |
if (!_.has(subHandles, subHandle.subscriptionId)) { | |
subHandles[subHandle.subscriptionId] = subHandle; | |
// Adding a new subscription will always cause us to transition from ready | |
// to not ready, but if we are already not ready then this can't make us | |
// ready. | |
if (this._allSubsReady) { | |
this._allSubsReadyDep.changed(); | |
} | |
} | |
return subHandle; | |
} | |
reactiveState() { | |
return reactiveFn() || {}; | |
} | |
subscriptionImplementation(args, options) { | |
const optionsObj = options || {}; | |
if (optionsObj.connection) { | |
this.subHandle = optionsObj.connection.subscribe.apply(optionsObj.connection, args); | |
} else { | |
this.subHandle = Meteor.subscribe.apply(Meteor, args); | |
} | |
return this.subHandle; | |
} | |
render() { | |
const propsToComp = Object.assign({}, this.props, this.reactiveState()); | |
if (this.subscriptionsReady()) { | |
return ( | |
<div> | |
<WrappedComponent {...propsToComp} /> | |
</div> | |
); | |
} | |
return ( | |
<div> | |
{this.props.loading || "Loading..."} | |
</div> | |
); | |
} | |
} | |
// TODO: add this to the composition not as a mixin | |
reactMixin(ComposedContainer.prototype, TrackerReact); | |
return ComposedContainer; | |
}; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment