accounts-frontend/packages/app/components/MeasureHeight.tsx
2020-05-24 02:08:24 +03:00

72 lines
2.3 KiB
TypeScript

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:
* <MeasureHeight
* state={theValueToInvalidateCurrentMeasure}
* onMeasure={this.onUpdateContextHeight}
* >
* <div>some content here</div>
* <div>which may be multiple children</div>
* </MeasureHeight>
*/
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<HTMLDivElement>
> {
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 <div {...props} ref={(el: HTMLDivElement) => (this.el = el)} />;
}
measure = () => {
requestAnimationFrame(() => {
this.el && this.props.onMeasure(this.el.offsetHeight);
});
};
enqueueMeasurement = debounce(this.measure);
}