From 4b4247193b86a7c4c61d9912e2b44b5df78d7518 Mon Sep 17 00:00:00 2001 From: YunBum Sung Date: Fri, 29 May 2020 10:09:03 +0900 Subject: [PATCH 1/4] Create CacheReactElementContext Enact-DCO-1.0-Signed-off-by: YB Sung (yb.sung@lge.com) --- .../CacheReactElementDecorator.js | 212 ++++++++++++++++++ .../CacheReactElementDecorator/package.json | 3 + 2 files changed, 215 insertions(+) create mode 100644 packages/ui/CacheReactElementDecorator/CacheReactElementDecorator.js create mode 100644 packages/ui/CacheReactElementDecorator/package.json diff --git a/packages/ui/CacheReactElementDecorator/CacheReactElementDecorator.js b/packages/ui/CacheReactElementDecorator/CacheReactElementDecorator.js new file mode 100644 index 0000000000..bdd61b3525 --- /dev/null +++ b/packages/ui/CacheReactElementDecorator/CacheReactElementDecorator.js @@ -0,0 +1,212 @@ +/** + * caches React elements, but allows context values to re-render. + * + * @module ui/CacheReactElementContext + * @exports CacheReactElementContext + */ + +import hoc from '@enact/core/hoc'; +import PropTypes from 'prop-types'; +import omit from 'ramda/src/omit'; +import pick from 'ramda/src/pick'; +import React from 'react'; + +const CacheReactElementContext = React.createContext(); + +const CacheReactElementWithChildrenContextDecorator = (property) => { + // eslint-disable-next-line no-shadow + function CacheReactElementWithChildrenContextDecorator ({children}) { + const context = React.useContext(CacheReactElementContext); + + return context && context[property] || children; + } + + return CacheReactElementWithChildrenContextDecorator; +}; + +/** + * A higher-order component that pass context as render props + * + * Example: + * ``` + * return ( + * + * {(props) => ( + * ); + * ``` + * + * @class CacheReactElementWithPropContext + * @memberof ui/CacheReactElementDecorator + * @hoc + * @private + */ +const CacheReactElementWithPropContext = ({filterProps}) => { + // eslint-disable-next-line no-shadow + function CacheReactElementWithPropContext ({cached, children}) { + if (cached) { + return ( + + {(props) => { + const cachedProps = pick(filterProps, props); + return children ? children(cachedProps) : null; + }} + + ); + } else { + return children && typeof children === 'function' ? children({}) : null; + } + } + + CacheReactElementWithPropContext.propTypes = /** @lends sandstone/ImageItem.CacheReactElementWithPropContext.prototype */ { + /** + * Cache React elements. + * + * @type {Boolean} + * @default true + * @public + */ + cached: PropTypes.bool + }; + + CacheReactElementWithPropContext.defaultProps = { + cached: true + }; + + return CacheReactElementWithPropContext; +}; + +const defaultWithPropConfig = { + filterProps: [] +}; + +const CacheReactElementWithPropContextDecorator = hoc(defaultWithPropConfig, (config, Wrapped) => { + const {filterProps} = config; + + // eslint-disable-next-line no-shadow + function CacheReactElementWithPropContextDecorator ({cached, ...rest}) { + const cachedContext = React.useContext(CacheReactElementContext); + if (cached) { + const cachedProps = pick(filterProps, cachedContext); + return ; + } else { + return ; + } + } + + CacheReactElementWithPropContextDecorator.propTypes = /** @lends sandstone/ImageItem.CacheReactElementWithPropContextDecorator.prototype */ { + /** + * Cache React elements. + * + * @type {Boolean} + * @default true + * @public + */ + cached: PropTypes.bool + }; + + CacheReactElementWithPropContextDecorator.defaultProps = { + cached: true + }; + + return CacheReactElementWithPropContextDecorator; +}); + +/** + * Default config for `CacheReactElementDecorator`. + * + * @memberof ui/ImageItem.CacheReactElementDecorator + * @hocconfig + * @private + */ +const defaultConfig = { + /** + * The array includes the key strings of the context object + * which will be used as children prop. + * + * @type {Boolean} + * @default false + * @public + */ + filterChildren: [] +}; + +/** + * A higher-order component that caches React elements, but allows context values to re-render. + * + * Example: + * ``` + * const ImageItemDecorator = compose( + * CacheReactElementDecorator({filterChildren: ['children', 'label']}), + * MarqueeController({marqueeOnFocus: true}), + * Spottable, + * Skinnable + * ); + * ``` + * + * @class CacheReactElementDecorator + * @memberof ui/ImageItem + * @hoc + * @private + */ +const CacheReactElementDecorator = hoc(defaultConfig, (config, Wrapped) => { + const {filterChildren} = config; + + // eslint-disable-next-line no-shadow + function CacheReactElementDecorator ({cached, ...rest}) { + const element = React.useRef(null); + + if (!cached) { + return ; + } + + const cachedProps = {}; + const pickProps = pick(filterChildren, rest); + const updatedProps = omit(filterChildren, rest); + + for (const key in pickProps) { + const CachedContextProp = CacheReactElementWithChildrenContextDecorator(key); + cachedProps[key] = {rest[key]}; + } + + element.current = element.current || ( + + ); + + return ( + + {element.current} + + ); + } + + CacheReactElementDecorator.propTypes = /** @lends sandstone/ImageItem.CacheReactElementDecorator.prototype */ { + /** + * Cache React elements. + * + * @type {Boolean} + * @default true + * @public + */ + cached: PropTypes.bool + }; + + CacheReactElementDecorator.defaultProps = { + cached: true + }; + + return CacheReactElementDecorator; +}); + +export default CacheReactElementDecorator; +export { + CacheReactElementContext, + CacheReactElementDecorator, + CacheReactElementWithChildrenContextDecorator, + CacheReactElementWithPropContext, + CacheReactElementWithPropContextDecorator +}; diff --git a/packages/ui/CacheReactElementDecorator/package.json b/packages/ui/CacheReactElementDecorator/package.json new file mode 100644 index 0000000000..f69f6289c3 --- /dev/null +++ b/packages/ui/CacheReactElementDecorator/package.json @@ -0,0 +1,3 @@ +{ + "main": "CacheReactElementDecorator.js" +} From 426bc84f0e00705c249125f28e5d09f5cf2f7bb8 Mon Sep 17 00:00:00 2001 From: YunBum Sung Date: Fri, 29 May 2020 10:09:26 +0900 Subject: [PATCH 2/4] Apply CacheReactElementWithPropContextDecorator to ImageItem Enact-DCO-1.0-Signed-off-by: YB Sung (yb.sung@lge.com) --- packages/ui/ImageItem/ImageItem.js | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/packages/ui/ImageItem/ImageItem.js b/packages/ui/ImageItem/ImageItem.js index 882deebca5..fb5b26bf8e 100644 --- a/packages/ui/ImageItem/ImageItem.js +++ b/packages/ui/ImageItem/ImageItem.js @@ -10,6 +10,7 @@ import kind from '@enact/core/kind'; import PropTypes from 'prop-types'; import React from 'react'; +import {CacheReactElementWithPropContextDecorator} from '../CacheReactElementDecorator'; import ComponentOverride from '../ComponentOverride'; import Image from '../Image'; import {Cell, Column, Row} from '../Layout'; @@ -36,6 +37,15 @@ const ImageItemBase = kind({ name: 'ui:ImageItem', propTypes: /** @lends ui/ImageItem.ImageItem.prototype */ { + /** + * Cache React elements. + * + * @type {Boolean} + * @default true + * @public + */ + cached: PropTypes.bool, + /** * The caption displayed with the image. * @@ -107,6 +117,7 @@ const ImageItemBase = kind({ }, defaultProps: { + cached: true, imageComponent: Image, orientation: 'vertical', selected: false @@ -126,15 +137,18 @@ const ImageItemBase = kind({ }) }, - render: ({children, css, imageComponent, orientation, placeholder, src, ...rest}) => { + render: ({cached, children, css, imageComponent, orientation, placeholder, src, ...rest}) => { delete rest.selected; const isHorizontal = orientation === 'horizontal'; const Component = isHorizontal ? Row : Column; + const ContextComponent = cached ? CacheReactElementWithPropContextDecorator({filterProps: ['data-index', 'src']})(Component) : Component; + const ContextCell = cached ? CacheReactElementWithPropContextDecorator({filterProps: ['src']})(Cell) : Cell; return ( - - + ) : null} - + ); } }); From fea7c89d7e2a03894d3e040bdf5455d6f6a94ca5 Mon Sep 17 00:00:00 2001 From: YunBum Sung Date: Fri, 29 May 2020 16:54:45 +0900 Subject: [PATCH 3/4] Create CacheReactElementAndUpdateDOMAttributesContextDecorator Enact-DCO-1.0-Signed-off-by: YB Sung (yb.sung@lge.com) --- .../CacheReactElementDecorator.js | 193 +++++++++++++++--- packages/ui/ImageItem/ImageItem.js | 15 +- 2 files changed, 168 insertions(+), 40 deletions(-) diff --git a/packages/ui/CacheReactElementDecorator/CacheReactElementDecorator.js b/packages/ui/CacheReactElementDecorator/CacheReactElementDecorator.js index bdd61b3525..db0e95398f 100644 --- a/packages/ui/CacheReactElementDecorator/CacheReactElementDecorator.js +++ b/packages/ui/CacheReactElementDecorator/CacheReactElementDecorator.js @@ -1,5 +1,5 @@ /** - * caches React elements, but allows context values to re-render. + * Use cached React elements, but allows props to be updated with a context. * * @module ui/CacheReactElementContext * @exports CacheReactElementContext @@ -10,40 +10,58 @@ import PropTypes from 'prop-types'; import omit from 'ramda/src/omit'; import pick from 'ramda/src/pick'; import React from 'react'; +import ReactDOM from 'react-dom'; const CacheReactElementContext = React.createContext(); -const CacheReactElementWithChildrenContextDecorator = (property) => { +/** + * A higher-order component that passes context as children props. + * + * Example: + * ``` + * const CachedContextProp = CacheReactElementAndUpdateChildrenContextDecorator(key); + * + * return ( + * {props[key]}; + * ); + * ``` + * + * @class CacheReactElementAndUpdatePropsContext + * @memberof ui/CacheReactElementDecorator + * @hoc + * @private + */ +const CacheReactElementAndUpdateChildrenContextDecorator = (property) => { // eslint-disable-next-line no-shadow - function CacheReactElementWithChildrenContextDecorator ({children}) { + function CacheReactElementAndUpdateChildrenContextDecorator ({children}) { const context = React.useContext(CacheReactElementContext); return context && context[property] || children; } - return CacheReactElementWithChildrenContextDecorator; + return CacheReactElementAndUpdateChildrenContextDecorator; }; /** - * A higher-order component that pass context as render props + * A higher-order component that passes context as render prop pattern's props. * * Example: * ``` * return ( - * + * * {(props) => ( + * * ); * ``` * - * @class CacheReactElementWithPropContext + * @class CacheReactElementAndUpdatePropsContext * @memberof ui/CacheReactElementDecorator * @hoc * @private */ -const CacheReactElementWithPropContext = ({filterProps}) => { +const CacheReactElementAndUpdatePropsContext = ({filterProps} = {filterProps: []}) => { // eslint-disable-next-line no-shadow - function CacheReactElementWithPropContext ({cached, children}) { + function CacheReactElementAndUpdatePropsContext ({cached, children}) { if (cached) { return ( @@ -58,7 +76,7 @@ const CacheReactElementWithPropContext = ({filterProps}) => { } } - CacheReactElementWithPropContext.propTypes = /** @lends sandstone/ImageItem.CacheReactElementWithPropContext.prototype */ { + CacheReactElementAndUpdatePropsContext.propTypes = /** @lends sandstone/CacheReactElementDecorator.CacheReactElementAndUpdatePropsContext.prototype */ { /** * Cache React elements. * @@ -69,22 +87,49 @@ const CacheReactElementWithPropContext = ({filterProps}) => { cached: PropTypes.bool }; - CacheReactElementWithPropContext.defaultProps = { + CacheReactElementAndUpdatePropsContext.defaultProps = { cached: true }; - return CacheReactElementWithPropContext; + return CacheReactElementAndUpdatePropsContext; }; const defaultWithPropConfig = { + /** + * The array includes the key strings of the context object + * which will be used as props. + * + * @type {Array} + * @default [] + * @public + */ filterProps: [] }; -const CacheReactElementWithPropContextDecorator = hoc(defaultWithPropConfig, (config, Wrapped) => { +/** + * A higher-order component that passes context as props. + * + * Example: + * ``` + * const ContextComponent = cached ? CacheReactElementAndUpdatePropsContextDecorator({filterProps: ['data-index', 'src']})(Component) : Component; + * + * return ( + * + *
+ * + * ); + * ``` + * + * @class CacheReactElementAndUpdatePropsContextDecorator + * @memberof ui/CacheReactElementDecorator + * @hoc + * @private + */ +const CacheReactElementAndUpdatePropsContextDecorator = hoc(defaultWithPropConfig, (config, Wrapped) => { const {filterProps} = config; // eslint-disable-next-line no-shadow - function CacheReactElementWithPropContextDecorator ({cached, ...rest}) { + function CacheReactElementAndUpdatePropsContextDecorator ({cached, ...rest}) { const cachedContext = React.useContext(CacheReactElementContext); if (cached) { const cachedProps = pick(filterProps, cachedContext); @@ -94,28 +139,109 @@ const CacheReactElementWithPropContextDecorator = hoc(defaultWithPropConfig, (co } } - CacheReactElementWithPropContextDecorator.propTypes = /** @lends sandstone/ImageItem.CacheReactElementWithPropContextDecorator.prototype */ { + CacheReactElementAndUpdatePropsContextDecorator.propTypes = /** @lends sandstone/CacheReactElementDecorator.CacheReactElementAndUpdatePropsContextDecorator.prototype */ { /** * Cache React elements. * * @type {Boolean} - * @default true + * @default false * @public */ cached: PropTypes.bool }; - CacheReactElementWithPropContextDecorator.defaultProps = { - cached: true + CacheReactElementAndUpdatePropsContextDecorator.defaultProps = { + cached: false }; - return CacheReactElementWithPropContextDecorator; + return CacheReactElementAndUpdatePropsContextDecorator; +}); + +/** + * A higher-order component that passes context to DOM attributes. + * + * Example: + * ``` + * const ContextComponent = cached ? CacheReactElementAndUpdateDOMAttributesContextDecorator({filterProps: ['data-index', 'src']})(Component) : Component; + * + * return ( + * + *
+ * + * ); + * ``` + * + * @class CacheReactElementAndUpdateDOMAttributesContextDecorator + * @memberof ui/CacheReactElementDecorator + * @hoc + * @private + */ +const CacheReactElementAndUpdateDOMAttributesContextDecorator = hoc(defaultWithPropConfig, (config, Wrapped) => { + const {filterProps} = config; + + // eslint-disable-next-line no-shadow + return class CacheReactElementAndUpdateDOMAttributesContextDecorator extends React.Component { + static propTypes = /** @lends sandstone/CacheReactElementDecorator.CacheReactElementAndUpdateDOMAttributesContextDecorator.prototype */ { + /** + * Cache React elements. + * + * @type {Boolean} + * @default false + * @public + */ + cached: PropTypes.bool + } + + static defaultProps = { + cached: false + } + + componentDidMount () { + this.updateDOMAttributes(); + } + + node = null + + cachedProps = {} + + cachedChildren = null + + updateDOMAttributes () { + this.node = this.node || ReactDOM.findDOMNode(this); // eslint-disable-line react/no-find-dom-node + + if (this.node) { + for (const prop in this.cachedProps) { + this.node.setAttribute(prop, this.cachedProps[prop]); + } + } + } + + render () { + const {cached, ...rest} = this.props; + + if (cached) { + return ( + + {(context) => { + this.cachedProps = pick(filterProps, context); + this.cachedChildren = this.cachedChildren || ; + this.updateDOMAttributes(); + + return this.cachedChildren; + }} + + ); + } else { + return ; + } + } + }; }); /** * Default config for `CacheReactElementDecorator`. * - * @memberof ui/ImageItem.CacheReactElementDecorator + * @memberof ui/CacheReactElementDecorator.CacheReactElementDecorator * @hocconfig * @private */ @@ -124,15 +250,15 @@ const defaultConfig = { * The array includes the key strings of the context object * which will be used as children prop. * - * @type {Boolean} - * @default false + * @type {Array} + * @default [] * @public */ filterChildren: [] }; /** - * A higher-order component that caches React elements, but allows context values to re-render. + * A higher-order component that use cached React elements, but allows props to be updated with a context. * * Example: * ``` @@ -145,7 +271,7 @@ const defaultConfig = { * ``` * * @class CacheReactElementDecorator - * @memberof ui/ImageItem + * @memberof ui/CacheReactElementDecorator * @hoc * @private */ @@ -165,7 +291,7 @@ const CacheReactElementDecorator = hoc(defaultConfig, (config, Wrapped) => { const updatedProps = omit(filterChildren, rest); for (const key in pickProps) { - const CachedContextProp = CacheReactElementWithChildrenContextDecorator(key); + const CachedContextProp = CacheReactElementAndUpdateChildrenContextDecorator(key); cachedProps[key] = {rest[key]}; } @@ -184,19 +310,19 @@ const CacheReactElementDecorator = hoc(defaultConfig, (config, Wrapped) => { ); } - CacheReactElementDecorator.propTypes = /** @lends sandstone/ImageItem.CacheReactElementDecorator.prototype */ { + CacheReactElementDecorator.propTypes = /** @lends sandstone/CacheReactElementDecorator.CacheReactElementDecorator.prototype */ { /** * Cache React elements. * * @type {Boolean} - * @default true + * @default false * @public */ cached: PropTypes.bool }; CacheReactElementDecorator.defaultProps = { - cached: true + cached: false }; return CacheReactElementDecorator; @@ -204,9 +330,10 @@ const CacheReactElementDecorator = hoc(defaultConfig, (config, Wrapped) => { export default CacheReactElementDecorator; export { + CacheReactElementAndUpdateChildrenContextDecorator, + CacheReactElementAndUpdateDOMAttributesContextDecorator, + CacheReactElementAndUpdatePropsContext, + CacheReactElementAndUpdatePropsContextDecorator, CacheReactElementContext, - CacheReactElementDecorator, - CacheReactElementWithChildrenContextDecorator, - CacheReactElementWithPropContext, - CacheReactElementWithPropContextDecorator + CacheReactElementDecorator }; diff --git a/packages/ui/ImageItem/ImageItem.js b/packages/ui/ImageItem/ImageItem.js index fb5b26bf8e..4a8f2ed23b 100644 --- a/packages/ui/ImageItem/ImageItem.js +++ b/packages/ui/ImageItem/ImageItem.js @@ -10,7 +10,7 @@ import kind from '@enact/core/kind'; import PropTypes from 'prop-types'; import React from 'react'; -import {CacheReactElementWithPropContextDecorator} from '../CacheReactElementDecorator'; +import {CacheReactElementAndUpdateDOMAttributesContextDecorator} from '../CacheReactElementDecorator'; import ComponentOverride from '../ComponentOverride'; import Image from '../Image'; import {Cell, Column, Row} from '../Layout'; @@ -41,7 +41,7 @@ const ImageItemBase = kind({ * Cache React elements. * * @type {Boolean} - * @default true + * @default false * @public */ cached: PropTypes.bool, @@ -117,7 +117,7 @@ const ImageItemBase = kind({ }, defaultProps: { - cached: true, + cached: false, imageComponent: Image, orientation: 'vertical', selected: false @@ -142,13 +142,14 @@ const ImageItemBase = kind({ const isHorizontal = orientation === 'horizontal'; const Component = isHorizontal ? Row : Column; - const ContextComponent = cached ? CacheReactElementWithPropContextDecorator({filterProps: ['data-index', 'src']})(Component) : Component; - const ContextCell = cached ? CacheReactElementWithPropContextDecorator({filterProps: ['src']})(Cell) : Cell; + const ContextComponent = cached ? CacheReactElementAndUpdateDOMAttributesContextDecorator({filterProps: ['data-index', 'src']})(Component) : Component; + const ContextCell = cached ? CacheReactElementAndUpdateDOMAttributesContextDecorator({filterProps: ['src']})(Cell) : Cell; + const cachedProp = cached ? {cached} : null; return ( - + Date: Tue, 2 Jun 2020 17:17:20 +0900 Subject: [PATCH 4/4] Cleanup Enact-DCO-1.0-Signed-off-by: YB Sung (yb.sung@lge.com) --- .../ui/CacheReactElementDecorator/CacheReactElementDecorator.js | 2 +- packages/ui/ImageItem/ImageItem.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ui/CacheReactElementDecorator/CacheReactElementDecorator.js b/packages/ui/CacheReactElementDecorator/CacheReactElementDecorator.js index db0e95398f..5fb7bd7a33 100644 --- a/packages/ui/CacheReactElementDecorator/CacheReactElementDecorator.js +++ b/packages/ui/CacheReactElementDecorator/CacheReactElementDecorator.js @@ -26,7 +26,7 @@ const CacheReactElementContext = React.createContext(); * ); * ``` * - * @class CacheReactElementAndUpdatePropsContext + * @class CacheReactElementAndUpdateChildrenContextDecorator * @memberof ui/CacheReactElementDecorator * @hoc * @private diff --git a/packages/ui/ImageItem/ImageItem.js b/packages/ui/ImageItem/ImageItem.js index 4a8f2ed23b..9933eaab50 100644 --- a/packages/ui/ImageItem/ImageItem.js +++ b/packages/ui/ImageItem/ImageItem.js @@ -142,7 +142,7 @@ const ImageItemBase = kind({ const isHorizontal = orientation === 'horizontal'; const Component = isHorizontal ? Row : Column; - const ContextComponent = cached ? CacheReactElementAndUpdateDOMAttributesContextDecorator({filterProps: ['data-index', 'src']})(Component) : Component; + const ContextComponent = cached ? CacheReactElementAndUpdateDOMAttributesContextDecorator({filterProps: ['data-index']})(Component) : Component; const ContextCell = cached ? CacheReactElementAndUpdateDOMAttributesContextDecorator({filterProps: ['src']})(Cell) : Cell; const cachedProp = cached ? {cached} : null;