UNPKG

react-broadcast

Version:

Reliably communicate state changes to deeply nested React elements

480 lines (368 loc) 13.3 kB
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 };