UNPKG

@gechiui/components

Version:
122 lines (100 loc) 3.9 kB
"use strict"; 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