class Update { constructor (state) { this.newState = state; this.effects = []; } state (update) { this.newState = { ...this.newState, update }; } effect (fn) { this.effects.push(fn); } } function assertHasUpdateMethod (component) { utils.assert( utils.isFunction(component.update), `withUpdates requires ${component.displayName} to implement an 'update' method` ); } function assertIsThenable (thenable, componentName) { utils.assert( thenable.then && utils.isFunction(thenable.then), `Scheduled effects must return thenables. Check the 'update' method of ${componentName}` ); } function effectsMixinForChannel (channel) { return { componentWillMount () { this.schedule(this.props, null, false); }, componentWillReceiveProps (nextProps) { this.schedule(this.props, nextProps, true); }, schedule (props, nextProps, isMounted) { assertHasUpdateMethod(this); const update = this.update(this.props, nextProps, new Update(this, channel), true); // Syncronously handle state updates this.setState(update.newState); // Schedule eventual updates this.effects.forEach((effect) => { const thenable = effect(); assertIsThenable(thenable, component.displayName); thenable.then(this.applyEffect, this.applyEffect); }); }, applyEffect (actionOrUpdate, ...additionalData) { // Promise returns either a string constant Action... if (utils.isString(actionOrUpdate)) { channel.publish(actionOrUpdate, { additionalData }); } // ...or an object to merge into component state if (utils.isObject(actionOrUpdate)) { this.setState(actionOrUpdate); } } }; } export { effectsMixinForChannel }; // default effects mixin publishes on default channel export default effectsMixinForChannel();