accounts-frontend/packages/app/components/MeasureHeight.tsx

72 lines
2.3 KiB
TypeScript
Raw Permalink Normal View History

2019-12-07 16:58:52 +05:30
import React from 'react';
import { omit, debounce } from 'app/functions';
2016-05-02 22:55:14 +05:30
/**
* 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.
2019-06-09 13:59:54 +05:30
* Both `shouldMeasure` and `state` can be used to reduce the amount of measures, which
* will reduce the count of forced reflows in browser.
2016-05-02 22:55:14 +05:30
*
* Usage:
* <MeasureHeight
* state={theValueToInvalidateCurrentMeasure}
* onMeasure={this.onUpdateContextHeight}
* >
* <div>some content here</div>
* <div>which may be multiple children</div>
* </MeasureHeight>
*/
2019-12-07 16:58:52 +05:30
type ChildState = any;
2019-12-10 13:17:32 +05:30
// TODO: this may be rewritten in more efficient way using resize/mutation observer
2019-12-07 16:58:52 +05:30
export default class MeasureHeight extends React.PureComponent<
2020-05-24 04:38:24 +05:30
{
shouldMeasure: (prevState: ChildState, newState: ChildState) => boolean;
onMeasure: (height: number) => void;
state: ChildState;
} & React.HTMLAttributes<HTMLDivElement>
2019-12-07 16:58:52 +05:30
> {
2020-05-24 04:38:24 +05:30
static defaultProps = {
shouldMeasure: (prevState: ChildState, newState: ChildState) => prevState !== newState,
onMeasure: () => {},
};
2016-05-02 22:55:14 +05:30
2020-05-24 04:38:24 +05:30
el: HTMLDivElement | null = null;
2016-05-02 22:55:14 +05:30
2020-05-24 04:38:24 +05:30
componentDidMount() {
// we want to measure height immediately on first mount to avoid ui laggs
this.measure();
window.addEventListener('resize', this.enqueueMeasurement);
}
2016-05-02 22:55:14 +05:30
2020-05-24 04:38:24 +05:30
componentDidUpdate(prevProps: typeof MeasureHeight.prototype.props) {
if (this.props.shouldMeasure(prevProps.state, this.props.state)) {
this.enqueueMeasurement();
}
2016-05-02 22:55:14 +05:30
}
2020-05-24 04:38:24 +05:30
componentWillUnmount() {
window.removeEventListener('resize', this.enqueueMeasurement);
}
2020-05-24 04:38:24 +05:30
render() {
const props = omit(this.props, ['shouldMeasure', 'onMeasure', 'state']);
2020-05-24 04:38:24 +05:30
return <div {...props} ref={(el: HTMLDivElement) => (this.el = el)} />;
}
2016-05-02 22:55:14 +05:30
2020-05-24 04:38:24 +05:30
measure = () => {
requestAnimationFrame(() => {
this.el && this.props.onMeasure(this.el.offsetHeight);
});
};
2020-05-24 04:38:24 +05:30
enqueueMeasurement = debounce(this.measure);
2016-05-02 22:55:14 +05:30
}