@gechiui/components
Version:
UI components for GeChiUI.
122 lines (100 loc) • 3.9 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = withFilters;
var _element = require("@gechiui/element");
var _lodash = require("lodash");
var _hooks = require("@gechiui/hooks");
var _compose = require("@gechiui/compose");
/**
* External dependencies
*/
/**
* GeChiUI dependencies
*/
const ANIMATION_FRAME_PERIOD = 16;
/**
* Creates a higher-order component which adds filtering capability to the
* wrapped component. Filters get applied when the original component is about
* to be mounted. When a filter is added or removed that matches the hook name,
* the wrapped component re-renders.
*
* @param {string} hookName Hook name exposed to be used by filters.
*
* @return {Function} Higher-order component factory.
*/
function withFilters(hookName) {
return (0, _compose.createHigherOrderComponent)(OriginalComponent => {
const namespace = 'core/with-filters/' + hookName;
/**
* The component definition with current filters applied. Each instance
* reuse this shared reference as an optimization to avoid excessive
* calls to `applyFilters` when many instances exist.
*
* @type {?Component}
*/
let FilteredComponent;
/**
* Initializes the FilteredComponent variable once, if not already
* assigned. Subsequent calls are effectively a noop.
*/
function ensureFilteredComponent() {
if (FilteredComponent === undefined) {
FilteredComponent = (0, _hooks.applyFilters)(hookName, OriginalComponent);
}
}
class FilteredComponentRenderer extends _element.Component {
constructor() {
super(...arguments);
ensureFilteredComponent();
}
componentDidMount() {
FilteredComponentRenderer.instances.push(this); // If there were previously no mounted instances for components
// filtered on this hook, add the hook handler.
if (FilteredComponentRenderer.instances.length === 1) {
(0, _hooks.addAction)('hookRemoved', namespace, onHooksUpdated);
(0, _hooks.addAction)('hookAdded', namespace, onHooksUpdated);
}
}
componentWillUnmount() {
FilteredComponentRenderer.instances = (0, _lodash.without)(FilteredComponentRenderer.instances, this); // If this was the last of the mounted components filtered on
// this hook, remove the hook handler.
if (FilteredComponentRenderer.instances.length === 0) {
(0, _hooks.removeAction)('hookRemoved', namespace);
(0, _hooks.removeAction)('hookAdded', namespace);
}
}
render() {
return (0, _element.createElement)(FilteredComponent, this.props);
}
}
FilteredComponentRenderer.instances = [];
/**
* Updates the FilteredComponent definition, forcing a render for each
* mounted instance. This occurs a maximum of once per animation frame.
*/
const throttledForceUpdate = (0, _lodash.debounce)(() => {
// Recreate the filtered component, only after delay so that it's
// computed once, even if many filters added.
FilteredComponent = (0, _hooks.applyFilters)(hookName, OriginalComponent); // Force each instance to render.
FilteredComponentRenderer.instances.forEach(instance => {
instance.forceUpdate();
});
}, ANIMATION_FRAME_PERIOD);
/**
* When a filter is added or removed for the matching hook name, each
* mounted instance should re-render with the new filters having been
* applied to the original component.
*
* @param {string} updatedHookName Name of the hook that was updated.
*/
function onHooksUpdated(updatedHookName) {
if (updatedHookName === hookName) {
throttledForceUpdate();
}
}
return FilteredComponentRenderer;
}, 'withFilters');
}
//# sourceMappingURL=index.js.map