@@ -12,7 +12,6 @@ import { useAnimatedContext } from '../../../context/AnimatedContext';
1212import { useEnteringCallbacks } from '../hooks/useEnteringCallbacks' ;
1313import { useVisibilityCallbacks } from '../hooks/useVisibilityCallbacks' ;
1414import { LazyChildProps } from '../types' ;
15- import { FRAME_MS } from '../../../constants' ;
1615
1716const SCREEN_HEIGHT = Dimensions . get ( 'window' ) . height ;
1817
@@ -34,15 +33,15 @@ export function FullLazyChild({
3433 } = useAnimatedContext ( ) ;
3534
3635 /**
37- * If onLayout returns a height value greater than 0.
36+ * If onLayout returns a height or width value greater than 0.
3837 */
39- const _canMeasure = useSharedValue ( false ) ;
38+ const _isJsLayoutComplete = useSharedValue ( false ) ;
4039 /**
4140 * LazyChild view ref.
4241 */
4342 const _viewRef = useAnimatedRef < Animated . View > ( ) ;
4443 /**
45- * Latest measure return.
44+ * Latest valid measure return or null .
4645 */
4746 const _measurement = useSharedValue < ReturnType < typeof measure > > ( null ) ;
4847
@@ -59,32 +58,35 @@ export function FullLazyChild({
5958 typeof onVisibilityExit === 'function'
6059 ) ;
6160
62- const _shouldMeasure = useDerivedValue (
61+ const _hasValidCallback = useDerivedValue (
6362 ( ) =>
6463 _shouldFireThresholdEnter . value ||
6564 _shouldFireThresholdExit . value ||
6665 _shouldMeasurePercentVisible . value ||
6766 _shouldFireVisibilityExit . value
6867 ) ;
6968
69+ function measureView ( ) {
70+ 'worklet' ;
71+ const measurement = measure ( _viewRef ) ;
72+
73+ if ( measurement && ( measurement ?. height || measurement ?. width ) ) {
74+ _measurement . value = measurement ;
75+ }
76+ }
77+
7078 useAnimatedReaction (
7179 ( ) => {
7280 // Track scollValue to make reaction fire. SCREEN_HEIGHT negative is to generously allow for overscroll.
73- if (
74- _canMeasure . value &&
75- _shouldMeasure . value &&
81+ return (
82+ _isJsLayoutComplete . value &&
83+ _hasValidCallback . value &&
7684 scrollValue . value > - SCREEN_HEIGHT
77- ) {
78- const measurement = measure ( _viewRef ) ;
79-
80- return measurement ;
81- }
82-
83- return null ;
85+ ) ;
8486 } ,
85- ( measured ) => {
86- if ( measured ?. height || measured ?. width ) {
87- _measurement . value = measured ;
87+ ( shouldMeasure ) => {
88+ if ( shouldMeasure ) {
89+ measureView ( ) ;
8890 }
8991 }
9092 ) ;
@@ -113,19 +115,15 @@ export function FullLazyChild({
113115 } ) ;
114116
115117 const onLayout = useCallback ( ( { nativeEvent } : LayoutChangeEvent ) => {
116- // Don't measure until we know we have something. This prevents those pesky Android measurement warnings.
117- // https://github.com/software-mansion/react-native-reanimated/blob/d8ef9c27c31dd2c32d4c3a2111326a448bf19ec9/packages/react-native-reanimated/src/platformFunctions/measure.ts#L95
118- if ( nativeEvent . layout . height > 0 ) {
119- _canMeasure . value = true ;
120- // Sometimes native measure runs too quick and return 0 on first paint.
121- setTimeout ( ( ) => {
118+ // Don't measure until we know we have something.
119+ if ( nativeEvent . layout . height > 0 || nativeEvent . layout . width > 0 ) {
120+ // onLayout runs when RN finishes render, but native layout may not be fully settled until the next frame.
121+ requestAnimationFrame ( ( ) => {
122122 runOnUI ( ( ) => {
123- const measurement = measure ( _viewRef ) ;
124- if ( measurement ) {
125- _measurement . value = measurement ;
126- }
123+ 'worklet' ;
124+ _isJsLayoutComplete . value = true ;
127125 } ) ( ) ;
128- } , FRAME_MS ) ;
126+ } ) ;
129127 }
130128 // eslint-disable-next-line react-hooks/exhaustive-deps -- shared values do not trigger re-renders
131129 } , [ ] ) ;
0 commit comments