Created
April 26, 2020 02:53
-
-
Save ferdaber/eed76efbd322774eadfebd9da2d4ff23 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { | |
| createContext, | |
| useRef, | |
| useContext, | |
| useCallback, | |
| useLayoutEffect, | |
| useEffect, | |
| ReactNode, | |
| createElement, | |
| } from "react"; | |
| import { useRerender } from "./use-state-variants"; | |
| export type MeasurableRefObject = { | |
| readonly current?: { | |
| clientHeight: number; | |
| clientWidth: number; | |
| getBoundingClientRect(): { x: number; y: number }; | |
| } | null; | |
| }; | |
| export type Dimensions = { | |
| height?: number; | |
| width?: number; | |
| x?: number; | |
| y?: number; | |
| }; | |
| const ContinuousPollingContext = createContext(false); | |
| /** | |
| * A hook that measures the dimensions of a ref's value and updates with every window resize | |
| * and with every component update. | |
| * | |
| * Use `useDimensions.windowRef` as the ref to measure the window's dimensions | |
| * | |
| * Use `continuousPolling` to force the hook to remeasure with every animation frame | |
| * | |
| * Any usage of the hook in a component that is inside `useDimensions.ContinuousPollingProvider` | |
| * will also remeasure with every animation frame | |
| * @example | |
| * <useDimensions.ContinuousPollingProvider> | |
| * <ComponentThatUsesUseDimensions /> // will rerender with every animation frame | |
| * </useDimensions.ContinuousPollingProvider> | |
| */ | |
| export function useDimensions( | |
| measurableRef: MeasurableRefObject, | |
| continuousPolling = false | |
| ) { | |
| const rerender = useRerender(); | |
| const dimensions = useRef<{ | |
| height?: number; | |
| width?: number; | |
| x?: number; | |
| y?: number; | |
| }>({}); | |
| if (useContext(ContinuousPollingContext)) { | |
| continuousPolling = true; | |
| } | |
| const measure = useCallback(() => { | |
| if (!measurableRef.current) return; | |
| const { height, width, x, y } = dimensions.current; | |
| const refHeight = measurableRef.current.clientHeight; | |
| const refWidth = measurableRef.current.clientWidth; | |
| const { x: refX, y: refY } = measurableRef.current.getBoundingClientRect(); | |
| dimensions.current.height = refHeight; | |
| dimensions.current.width = refWidth; | |
| dimensions.current.x = refX; | |
| dimensions.current.y = refY; | |
| if (refHeight !== height || refWidth !== width || refX !== x || refY !== y) | |
| rerender(); | |
| }, [measurableRef, rerender]); | |
| useLayoutEffect(() => { | |
| try { | |
| measure(); | |
| } catch (e) { | |
| // https://github.com/facebook/react/blob/da834083cccb6ef942f701c6b6cecc78213196a8/packages/react-reconciler/src/ReactFiberWorkLoop.js#L2614 | |
| console.error( | |
| "useDimensions crashed while attempting to re-measure. This is likely an infinite loop." | |
| ); | |
| console.error(e); | |
| } | |
| }); | |
| useEffect(() => { | |
| window.addEventListener("resize", measure); | |
| return () => { | |
| window.removeEventListener("resize", measure); | |
| }; | |
| }, [measure]); | |
| useEffect(() => { | |
| if (!continuousPolling) return; | |
| let aborted = false; | |
| const cb = () => { | |
| measure(); | |
| if (!aborted) window.requestAnimationFrame(cb); | |
| }; | |
| window.requestAnimationFrame(cb); | |
| return () => { | |
| aborted = true; | |
| }; | |
| }, [continuousPolling, measure]); | |
| return { measure, ...dimensions.current }; | |
| } | |
| useDimensions.windowRef = { | |
| current: { | |
| get clientHeight() { | |
| return window.innerHeight; | |
| }, | |
| get clientWidth() { | |
| return window.innerWidth; | |
| }, | |
| getBoundingClientRect() { | |
| return { | |
| x: 0, | |
| y: 0, | |
| }; | |
| }, | |
| }, | |
| }; | |
| useDimensions.ContinuousPollingProvider = (props: { | |
| isEnabled?: boolean; | |
| children?: ReactNode; | |
| }) => | |
| createElement(ContinuousPollingContext.Provider, { | |
| children: props.children, | |
| value: props.isEnabled ?? true, | |
| }) as JSX.Element; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment