From 8dcd16081777a0e26d6bea2e6c67f254b36a2003 Mon Sep 17 00:00:00 2001 From: Vesa Luusua Date: Wed, 31 Oct 2018 00:18:08 +0200 Subject: [PATCH] withDimensions context helper --- src/util/contextHelpers.js | 106 +++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/src/util/contextHelpers.js b/src/util/contextHelpers.js index a72b0da6..9f82c906 100644 --- a/src/util/contextHelpers.js +++ b/src/util/contextHelpers.js @@ -69,6 +69,112 @@ export const withViewport = Component => { return WithViewportComponent; }; +/** + * A higher order component (HOC) that provides dimensions to the wrapped component as a + * `dimensions` prop that has the shape `{ width: 600, height: 400}`. + * + * @param {React.Component} Component to be wrapped by this HOC + * @param {Object} options pass in options like maxWidth and maxHeight. + * + * @return {Object} HOC component which knows its dimensions + */ +export const withDimensions = (Component, options = {}) => { + // The resize event is flooded when the browser is resized. We'll + // use a small timeout to throttle changing the viewport since it + // will trigger rerendering. + const THROTTLE_WAIT_MS = 200; + // First render default wait after mounting (small wait for styled paint) + const RENDER_WAIT_MS = 100; + + class WithDimensionsComponent extends ReactComponent { + constructor(props) { + super(props); + this.element = null; + this.defaultRenderTimeout = null; + + this.state = { width: 0, height: 0 }; + + this.handleWindowResize = throttle(this.handleWindowResize.bind(this), THROTTLE_WAIT_MS); + this.setDimensions = this.setDimensions.bind(this); + } + + componentDidMount() { + window.addEventListener('resize', this.handleWindowResize); + window.addEventListener('orientationchange', this.handleWindowResize); + + this.defaultRenderTimeout = window.setTimeout(() => { + this.setDimensions(); + }, RENDER_WAIT_MS); + } + + componentWillUnmount() { + window.removeEventListener('resize', this.handleWindowResize); + window.removeEventListener('orientationchange', this.handleWindowResize); + window.clearTimeout(this.defaultRenderTimeout); + } + + handleWindowResize() { + window.requestAnimationFrame(() => { + this.setDimensions(); + }); + } + + setDimensions() { + this.setState(prevState => { + const { clientWidth, clientHeight } = this.element || { clientWidth: 0, clientHeight: 0 }; + return { width: clientWidth, height: clientHeight }; + }); + } + + render() { + // Dimensions from state (i.e. dimension after previous resize) + // These are needed for component rerenders + const { width, height } = this.state; + + // Current dimensions from element reference + const { clientWidth, clientHeight } = this.element || { clientWidth: 0, clientHeight: 0 }; + const hasDimensions = + (width !== 0 && height !== 0) || (clientWidth !== 0 && clientHeight !== 0); + + // clientWidth and clientHeight + const currentDimensions = + clientWidth !== 0 && clientHeight !== 0 + ? { width: clientWidth, height: clientHeight } + : width !== 0 && height !== 0 + ? { width, height } + : {}; + + const props = { ...this.props, dimensions: currentDimensions }; + + // lazyLoadWithDimensions HOC needs to take all given space + // unless max dimensions are provided through options. + const { maxWidth, maxHeight } = options; + const maxWidthMaybe = maxWidth ? { maxWidth } : {}; + const maxHeightMaybe = maxHeight ? { maxHeight } : {}; + const style = + maxWidth || maxHeight + ? { width: '100%', height: '100%', ...maxWidthMaybe, ...maxHeightMaybe } + : { position: 'absolute', top: 0, right: 0, bottom: 0, left: 0 }; + + return ( +
{ + this.element = element; + }} + style={style} + > + {hasDimensions ? : null} +
+ ); + } + } + + WithDimensionsComponent.displayName = `withDimensions(${Component.displayName || + Component.name})`; + + return WithDimensionsComponent; +}; + /** * A higher order component (HOC) that lazy loads the current element and provides * dimensions to the wrapped component as a `dimensions` prop that has