withDimensions context helper

This commit is contained in:
Vesa Luusua 2018-10-31 00:18:08 +02:00
parent d81a4b33ae
commit 8dcd160817

View file

@ -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 (
<div
ref={element => {
this.element = element;
}}
style={style}
>
{hasDimensions ? <Component {...props} /> : null}
</div>
);
}
}
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