···1616 useSharedValue,
1717} from 'react-native-reanimated'
18181919-import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
2019import {ScrollProvider} from '#/lib/ScrollContext'
2120import {
2221 Pager,
···7271 const headerHeight = headerOnlyHeight + tabBarHeight
73727473 // capture the header bar sizing
7575- const onTabBarLayout = useNonReactiveCallback((evt: LayoutChangeEvent) => {
7474+ const onTabBarLayout = useCallback((evt: LayoutChangeEvent) => {
7675 const height = evt.nativeEvent.layout.height
7776 if (height > 0) {
7877 // The rounding is necessary to prevent jumps on iOS
7978 setTabBarHeight(Math.round(height * 2) / 2)
8079 }
8181- })
8282- const onHeaderOnlyLayout = useNonReactiveCallback((height: number) => {
8080+ }, [])
8181+ const onHeaderOnlyLayout = useCallback((height: number) => {
8382 if (height > 0) {
8483 // The rounding is necessary to prevent jumps on iOS
8584 setHeaderOnlyHeight(Math.round(height * 2) / 2)
8685 }
8787- })
8686+ }, [])
88878988 const renderTabBar = useCallback(
9089 (props: RenderTabBarFnProps) => {
···270269 ],
271270 }
272271 })
273273- const headerRef = useRef(null)
272272+ const headerRef = useRef<View>(null)
273273+ const fallbackHeaderOnlyHeight = useRef(0)
274274 return (
275275 <Animated.View
276276 pointerEvents={IS_IOS ? 'auto' : 'box-none'}
···278278 <View
279279 ref={headerRef}
280280 pointerEvents={IS_IOS ? 'auto' : 'box-none'}
281281- collapsable={false}>
281281+ collapsable={false}
282282+ onLayout={(e: LayoutChangeEvent) => {
283283+ // Fallback measurement using onLayout directly on the header wrapper.
284284+ // This is more reliable than .measure() on Android after certain
285285+ // navigation transitions (e.g. returning from the logged-out view)
286286+ // where .measure() can fail to return a height. in general though,
287287+ // we should prefer using .measure() when possible as this can
288288+ // fire too early and cause layout thrashing.
289289+ // ref: https://github.com/bluesky-social/social-app/pull/9964 -sfp
290290+ if (isHeaderReady) {
291291+ fallbackHeaderOnlyHeight.current = e.nativeEvent.layout.height
292292+ }
293293+ }}>
282294 {renderHeader?.({setMinimumHeight: setMinimumHeaderHeight})}
283295 {
284296 // It wouldn't be enough to place `onLayout` on the parent node because
···286298 // Instead, we'll render a brand node conditionally and get fresh layout.
287299 isHeaderReady && (
288300 <View
301301+ collapsable={false}
289302 // It wouldn't be enough to do this in a `ref` of an effect because,
290303 // even if `isHeaderReady` might have turned `true`, the associated
291304 // layout might not have been performed yet on the native side.
292305 onLayout={() => {
293293- // @ts-ignore
294306 headerRef.current?.measure(
295307 (_x: number, _y: number, _width: number, height: number) => {
296296- onHeaderOnlyLayout(height)
308308+ // sometimes height is `undefined` on Android, see above
309309+ if (height !== undefined) {
310310+ onHeaderOnlyLayout(height)
311311+ } else {
312312+ // if measure fails, use the value we got from `onLayout`
313313+ onHeaderOnlyLayout(fallbackHeaderOnlyHeight.current)
314314+ }
297315 },
298316 )
299317 }}