react-broadcast
Version:
Reliably communicate state changes to deeply nested React elements
480 lines (368 loc) • 13.3 kB
JavaScript
import React from 'react';
import PropTypes from 'prop-types';
import warning from 'warning';
/**
* Copyright 2013-2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
/**
* Use invariant() to assert state which your program assumes to be true.
*
* Provide sprintf-style format (only %s is supported) and arguments
* to provide information about what broke and what you were
* expecting.
*
* The invariant message will be stripped in production, but the invariant
* will remain to ensure logic does not differ in production.
*/
var NODE_ENV = process.env.NODE_ENV;
var invariant = function(condition, format, a, b, c, d, e, f) {
if (NODE_ENV !== 'production') {
if (format === undefined) {
throw new Error('invariant requires an error message argument');
}
}
if (!condition) {
var error;
if (format === undefined) {
error = new Error(
'Minified exception occurred; use the non-minified dev environment ' +
'for the full error message and additional helpful warnings.'
);
} else {
var args = [a, b, c, d, e, f];
var argIndex = 0;
error = new Error(
format.replace(/%s/g, function() { return args[argIndex++]; })
);
error.name = 'Invariant Violation';
}
error.framesToPop = 1; // we don't care about invariant's own frame
throw error;
}
};
var invariant_1 = invariant;
function createDeprecationWarning() {
var alreadyWarned = false;
return function (message) {
warning(alreadyWarned, message);
alreadyWarned = true;
};
}
var classCallCheck = function (instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
};
var _extends = Object.assign || function (target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
return target;
};
var inherits = function (subClass, superClass) {
if (typeof superClass !== "function" && superClass !== null) {
throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
}
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: {
value: subClass,
enumerable: false,
writable: true,
configurable: true
}
});
if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
};
var possibleConstructorReturn = function (self, call) {
if (!self) {
throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
}
return call && (typeof call === "object" || typeof call === "function") ? call : self;
};
var deprecationWarning = createDeprecationWarning();
function createBroadcast(initialValue) {
var currentValue = initialValue;
var subscribers = [];
var getValue = function getValue() {
return currentValue;
};
var publish = function publish(state) {
currentValue = state;
subscribers.forEach(function (s) {
return s(currentValue);
});
};
var subscribe = function subscribe(subscriber) {
subscribers.push(subscriber);
return function () {
subscribers = subscribers.filter(function (s) {
return s !== subscriber;
});
};
};
return {
getValue: getValue,
publish: publish,
subscribe: subscribe
};
}
/**
* A <Broadcast> provides a generic way for descendants to "subscribe"
* to some value that changes over time, bypassing any intermediate
* shouldComponentUpdate's in the hierarchy. It puts all subscription
* functions on context.broadcasts, keyed by "channel".
*
* To use it, a subscriber must opt-in to context.broadcasts. See the
* <Subscriber> component for a reference implementation.
*/
var Broadcast = function (_React$Component) {
inherits(Broadcast, _React$Component);
function Broadcast() {
var _temp, _this, _ret;
classCallCheck(this, Broadcast);
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
return _ret = (_temp = (_this = possibleConstructorReturn(this, _React$Component.call.apply(_React$Component, [this].concat(args))), _this), _this.broadcast = createBroadcast(_this.props.value), _temp), possibleConstructorReturn(_this, _ret);
}
Broadcast.prototype.getChildContext = function getChildContext() {
var _babelHelpers$extends;
return {
broadcasts: _extends({}, this.context.broadcasts, (_babelHelpers$extends = {}, _babelHelpers$extends[this.props.channel] = this.broadcast, _babelHelpers$extends))
};
};
Broadcast.prototype.componentWillMount = function componentWillMount() {
deprecationWarning("<Broadcast> is deprecated and will be removed in the next major release. " + "Please use createContext instead. See https://goo.gl/QAF37J for more info.");
};
Broadcast.prototype.componentWillReceiveProps = function componentWillReceiveProps(nextProps) {
invariant_1(this.props.channel === nextProps.channel, "You cannot change <Broadcast channel>");
if (!this.props.compareValues(this.props.value, nextProps.value)) {
this.broadcast.publish(nextProps.value);
}
};
Broadcast.prototype.render = function render() {
return React.Children.only(this.props.children);
};
return Broadcast;
}(React.Component);
Broadcast.propTypes = {
channel: PropTypes.string.isRequired,
children: PropTypes.node.isRequired,
compareValues: PropTypes.func,
value: PropTypes.any
};
Broadcast.defaultProps = {
compareValues: function compareValues(prevValue, nextValue) {
return prevValue === nextValue;
}
};
Broadcast.contextTypes = {
broadcasts: PropTypes.object
};
Broadcast.childContextTypes = {
broadcasts: PropTypes.object.isRequired
};
var deprecationWarning$1 = createDeprecationWarning();
/**
* A <Subscriber> pulls the value for a channel off of context.broadcasts
* and passes it to its children function.
*/
var Subscriber = function (_React$Component) {
inherits(Subscriber, _React$Component);
function Subscriber() {
var _temp, _this, _ret;
classCallCheck(this, Subscriber);
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
return _ret = (_temp = (_this = possibleConstructorReturn(this, _React$Component.call.apply(_React$Component, [this].concat(args))), _this), _this.state = {
value: undefined
}, _temp), possibleConstructorReturn(_this, _ret);
}
Subscriber.prototype.getBroadcast = function getBroadcast() {
var broadcasts = this.context.broadcasts || {};
var broadcast = broadcasts[this.props.channel];
invariant_1(this.props.quiet || broadcast, '<Subscriber channel="%s"> must be rendered in the context of a <Broadcast channel="%s">', this.props.channel, this.props.channel);
return broadcast;
};
Subscriber.prototype.componentWillMount = function componentWillMount() {
deprecationWarning$1("<Subscriber> is deprecated and will be removed in the next major release. " + "Please use createContext instead. See https://goo.gl/QAF37J for more info.");
var broadcast = this.getBroadcast();
if (broadcast) {
this.setState({
value: broadcast.getValue()
});
}
};
Subscriber.prototype.componentDidMount = function componentDidMount() {
var _this2 = this;
var broadcast = this.getBroadcast();
if (broadcast) {
this.unsubscribe = broadcast.subscribe(function (value) {
_this2.setState({ value: value });
});
}
};
Subscriber.prototype.componentWillUnmount = function componentWillUnmount() {
if (this.unsubscribe) this.unsubscribe();
};
Subscriber.prototype.render = function render() {
var children = this.props.children;
return children ? children(this.state.value) : null;
};
return Subscriber;
}(React.Component);
Subscriber.propTypes = {
channel: PropTypes.string.isRequired,
children: PropTypes.func,
quiet: PropTypes.bool
};
Subscriber.defaultProps = {
quiet: false
};
Subscriber.contextTypes = {
broadcasts: PropTypes.object
};
// TODO: Swap this out for Symbol once we don't need a shim for it.
var uid = 1;
function createContext(defaultValue) {
var channel = uid++;
/**
* A <Provider> is a container for a "value" that its <Consumer>
* may subscribe to.
*/
var Provider = function (_React$Component) {
inherits(Provider, _React$Component);
function Provider() {
var _temp, _this, _ret;
classCallCheck(this, Provider);
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
return _ret = (_temp = (_this = possibleConstructorReturn(this, _React$Component.call.apply(_React$Component, [this].concat(args))), _this), _this.subscribers = [], _this.publish = function (value) {
_this.subscribers.forEach(function (s) {
return s(value);
});
}, _this.subscribe = function (subscriber) {
_this.subscribers.push(subscriber);
return function () {
_this.subscribers = _this.subscribers.filter(function (s) {
return s !== subscriber;
});
};
}, _this.getValue = function () {
return _this.props.value;
}, _temp), possibleConstructorReturn(_this, _ret);
}
/**
* For convenience when setting up a component that tracks this
* <Provider>'s value in state.
*
* const { Provider, Consumer } = createContext("default value")
*
* class MyComponent {
* state = {
* value: Provider.defaultValue
* }
*
* // ...
*
* render() {
* return <Provider value={this.state.value}/>
* }
* }
*/
Provider.prototype.getChildContext = function getChildContext() {
var _babelHelpers$extends;
return {
broadcasts: _extends({}, this.context.broadcasts, (_babelHelpers$extends = {}, _babelHelpers$extends[channel] = {
subscribe: this.subscribe,
getValue: this.getValue
}, _babelHelpers$extends))
};
};
Provider.prototype.componentWillReceiveProps = function componentWillReceiveProps(nextProps) {
if (this.props.value !== nextProps.value) {
this.publish(nextProps.value);
}
};
Provider.prototype.render = function render() {
return this.props.children;
};
return Provider;
}(React.Component);
/**
* A <Consumer> sets state whenever its <Provider value> changes
* and calls its render prop with the result.
*/
Provider.defaultValue = defaultValue;
Provider.propTypes = {
children: PropTypes.node,
value: PropTypes.any
};
Provider.defaultProps = {
value: defaultValue
};
Provider.contextTypes = {
broadcasts: PropTypes.object
};
Provider.childContextTypes = {
broadcasts: PropTypes.object.isRequired
};
var Consumer = function (_React$Component2) {
inherits(Consumer, _React$Component2);
function Consumer() {
var _temp2, _this2, _ret2;
classCallCheck(this, Consumer);
for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
args[_key2] = arguments[_key2];
}
return _ret2 = (_temp2 = (_this2 = possibleConstructorReturn(this, _React$Component2.call.apply(_React$Component2, [this].concat(args))), _this2), _this2.broadcast = _this2.context.broadcasts && _this2.context.broadcasts[channel], _this2.state = {
value: _this2.broadcast ? _this2.broadcast.getValue() : defaultValue
}, _temp2), possibleConstructorReturn(_this2, _ret2);
}
Consumer.prototype.componentDidMount = function componentDidMount() {
var _this3 = this;
if (this.broadcast) {
this.unsubscribe = this.broadcast.subscribe(function (value) {
_this3.setState({ value: value });
});
} else {
warning(this.props.quiet, "<Consumer> was rendered outside the context of its <Provider>");
}
};
Consumer.prototype.componentWillUnmount = function componentWillUnmount() {
if (this.unsubscribe) this.unsubscribe();
};
Consumer.prototype.render = function render() {
var children = this.props.children;
return children ? children(this.state.value) : null;
};
return Consumer;
}(React.Component);
Consumer.contextTypes = {
broadcasts: PropTypes.object
};
Consumer.propTypes = {
children: PropTypes.func,
quiet: PropTypes.bool
};
Consumer.defaultProps = {
quiet: false
};
return {
Provider: Provider,
Consumer: Consumer
};
}
export { Broadcast, Subscriber, createContext };