import { ComponentClass, ComponentProps, Component, RefObject, createRef } from "react"; import debounce from "lodash-es/debounce"; const getDisplayName =

>( WrappedComponent: ComponentClass

) => { return WrappedComponent.displayName || WrappedComponent.name || "Component"; }; export interface MeasureElementProps { heightFromMeasureElementHOC: number | null; widthFromMeasureElementHOC: number | null; } interface MeasureElementComponent

extends Component< P & MeasureElementProps > { refToMeasure: RefObject | HTMLElement | null; } interface MeasureElementClass

{ new ( props: P & MeasureElementProps, context?: any ): MeasureElementComponent

; } interface IState { height: number | null; width: number | null; } /* HOC to check component width & supply updated widths as a prop (stored interally via state) Ensure the wrapped component contains a reference to the element you wish to measure the width of, typically the root element - ensure the ref is a HTML element and not a custom class component, as we want to use the DOM API Set the ref via a callback ref ```js ref={component => (this.refToMeasure = component)}> ``` or Set the ref via createRef `React.createRef()` in constructor, then setting via ```js ref={this.refToMeasure}> ``` */ const measureElement =

>( WrappedComponent: MeasureElementClass

, verbose = true ) => { class MeasureElement extends Component { wrappedComponent = createRef>(); checkAndUpdateSizingWithDebounce: () => void; static displayName = `MeasureElement(${getDisplayName(WrappedComponent)})`; constructor(props: P) { super(props); this.state = { width: null, height: null }; this.checkAndUpdateSizing = this.checkAndUpdateSizing.bind(this); this.checkAndUpdateSizingWithDebounce = debounce( this.checkAndUpdateSizing, 200 ); } componentDidMount() { window.addEventListener("resize", this.checkAndUpdateSizingWithDebounce); this.checkAndUpdateSizing(); } componentDidUpdate() { this.checkAndUpdateSizing(); } componentWillUnmount() { window.removeEventListener( "resize", this.checkAndUpdateSizingWithDebounce ); } checkAndUpdateSizing() { if (!this.wrappedComponent.current) { return; } const refToUse = this.wrappedComponent.current.refToMeasure; const widthFromRef = refToUse ? "current" in refToUse ? refToUse.current?.clientWidth : refToUse.clientWidth : undefined; const heightFromRef = refToUse ? "current" in refToUse ? refToUse.current?.clientHeight : refToUse.clientHeight : undefined; const newWidth = widthFromRef || 0; const newHeight = heightFromRef || 0; if (verbose) { if (!widthFromRef || !heightFromRef) console.warn("measureElement was used without a ref to measure"); // not sure if we warn on height = 0? if (newWidth === 0 || newHeight === 0) console.warn("measureElement has a reading of 0"); } // if we haven't already set the width, or if the current width doesn't match the newly rendered width if ( this.wrappedComponent.current && this.wrappedComponent.current.refToMeasure ) { if ( // if we get into a null state this has a path of setting infinitely(?) // !this.state.height || // !this.state.width || this.state.height !== newHeight || this.state.width !== newWidth ) { this.setState({ width: newWidth, height: newHeight }); } } } render() { return ( ); } } return MeasureElement; }; export default measureElement;