import React from 'react'; import { omit, debounce } from 'app/functions'; /** * MeasureHeight is a component that allows you to measure the height of elements wrapped. * * Each time the height changed, the `onMeasure` prop will be called. * On each component update the `shouldMeasure` prop is being called and depending of * the value returned will be decided whether to call `onMeasure`. * By default `shouldMeasure` will compare the old and new values of the `state` prop. * Both `shouldMeasure` and `state` can be used to reduce the amount of measures, which * will reduce the count of forced reflows in browser. * * Usage: * *
some content here
*
which may be multiple children
*
*/ type ChildState = any; // TODO: this may be rewritten in more efficient way using resize/mutation observer export default class MeasureHeight extends React.PureComponent< { shouldMeasure: (prevState: ChildState, newState: ChildState) => boolean; onMeasure: (height: number) => void; state: ChildState; } & React.HTMLAttributes > { static defaultProps = { shouldMeasure: (prevState: ChildState, newState: ChildState) => prevState !== newState, onMeasure: () => {}, }; el: HTMLDivElement | null = null; componentDidMount() { // we want to measure height immediately on first mount to avoid ui laggs this.measure(); window.addEventListener('resize', this.enqueueMeasurement); } componentDidUpdate(prevProps: typeof MeasureHeight.prototype.props) { if (this.props.shouldMeasure(prevProps.state, this.props.state)) { this.enqueueMeasurement(); } } componentWillUnmount() { window.removeEventListener('resize', this.enqueueMeasurement); } render() { const props = omit(this.props, ['shouldMeasure', 'onMeasure', 'state']); return
(this.el = el)} />; } measure = () => { requestAnimationFrame(() => { this.el && this.props.onMeasure(this.el.offsetHeight); }); }; enqueueMeasurement = debounce(this.measure); }