import React, { memo, PropsWithChildren, useCallback, useMemo, useRef, useState, } from "react"; import { View, StyleSheet } from "react-native"; import { v4 } from "uuid"; type Props = { Component: React.ElementType; onLayout: (LayoutChangeEvent) => void; componentProps?: Record; }; const isError = (possibleError): boolean => possibleError instanceof Error; const styles = StyleSheet.create({ container: { position: "absolute", opacity: 0, top: 0, left: 0, right: 0, bottom: 0, }, wrapper: {}, }); const MeasurementsPortal = ({ Component, onLayout, componentProps }: Props) => { return ( ); }; type MeasurementPortalContextType = { measureComponent?: ( comp: React.ElementType, props: Record ) => Promise; measuringInProgress?: boolean; }; const MeasurementPortalContext = React.createContext(null); const MeasurementsPortalContextProvider = memo( ({ children }: PropsWithChildren) => { const Component = useRef(View); const [measuringInProgress, setMeasuringInProgress] = useState(false); const measureComponentCallback = useRef(null); const componentProps = useRef(null); const onLoadFinishedIsCalled = useRef(null); const setComponent = useCallback((comp) => { Component.current = comp; }, []); const setMeasureComponentCallback = useCallback((cb) => { measureComponentCallback.current = cb; }, []); const finalize = useCallback(({ width, height }) => { measureComponentCallback?.current?.({ width, height, index: componentProps.current.index, }); setMeasureComponentCallback(null); setMeasuringInProgress(false); onLoadFinishedIsCalled.current = false; }, []); const setComponentProps = useCallback((props) => { const handleOnLoadFinish = (...args) => { const error = args[0]?.error; if (isError(error)) { finalize({ width: 0, height: 0 }); } else { onLoadFinishedIsCalled.current = true; props.onLoadFinished(...args); } }; componentProps.current = { ...props, onLoadFinished: handleOnLoadFinish, }; }, []); const measureComponent = useCallback(async (comp, props) => { return new Promise((resolve) => { setMeasureComponentCallback(resolve); setMeasuringInProgress(true); setComponentProps(props); setComponent(comp); }); }, []); const onLayout = useCallback( ({ nativeEvent: { layout: { width, height }, }, }) => { if ( measureComponentCallback.current && onLoadFinishedIsCalled.current ) { finalize({ width, height, }); } }, [] ); const contextValue = useMemo( () => ({ measureComponent, measuringInProgress, }), [measuringInProgress] ); return ( <> {children} ); } ); export { MeasurementsPortal, MeasurementsPortalContextProvider, MeasurementPortalContext, };