Skip to content

Commit

Permalink
Add useHandleRef as a lighter alternative for useAnimatedRef (#6500)
Browse files Browse the repository at this point in the history
  • Loading branch information
gaearon authored Nov 18, 2024
1 parent 4f0f9eb commit 7b6c182
Show file tree
Hide file tree
Showing 7 changed files with 75 additions and 54 deletions.
39 changes: 39 additions & 0 deletions src/lib/hooks/useHandleRef.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import {useState} from 'react'
import {AnimatedRef, measure, MeasuredDimensions} from 'react-native-reanimated'

export type HandleRef = {
(node: any): void
current: null | number
}

// This is a lighterweight alternative to `useAnimatedRef()` for imperative UI thread actions.
// Render it like <View ref={ref} />, then pass `ref.current` to `measureHandle()` and such.
export function useHandleRef(): HandleRef {
return useState(() => {
const ref = (node: any) => {
if (node) {
ref.current =
node._nativeTag ??
node.__nativeTag ??
node.canonical?.nativeTag ??
null
} else {
ref.current = null
}
}
ref.current = null
return ref
})[0] as HandleRef
}

// When using this version, you need to read ref.current on the JS thread, and pass it to UI.
export function measureHandle(
current: number | null,
): MeasuredDimensions | null {
'worklet'
if (current !== null) {
return measure((() => current) as AnimatedRef<any>)
} else {
return null
}
}
18 changes: 7 additions & 11 deletions src/screens/Profile/Header/Shell.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,14 @@
import React, {memo} from 'react'
import {StyleSheet, TouchableWithoutFeedback, View} from 'react-native'
import Animated, {
measure,
MeasuredDimensions,
runOnJS,
runOnUI,
useAnimatedRef,
} from 'react-native-reanimated'
import {MeasuredDimensions, runOnJS, runOnUI} from 'react-native-reanimated'
import {AppBskyActorDefs, ModerationDecision} from '@atproto/api'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useNavigation} from '@react-navigation/native'

import {BACK_HITSLOP} from '#/lib/constants'
import {measureHandle, useHandleRef} from '#/lib/hooks/useHandleRef'
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
import {NavigationProp} from '#/lib/routes/types'
import {isIOS} from '#/platform/detection'
Expand Down Expand Up @@ -49,7 +44,7 @@ let ProfileHeaderShell = ({
const {openLightbox} = useLightboxControls()
const navigation = useNavigation<NavigationProp>()
const {isDesktop} = useWebMediaQueries()
const aviRef = useAnimatedRef()
const aviRef = useHandleRef()

const onPressBack = React.useCallback(() => {
if (navigation.canGoBack()) {
Expand Down Expand Up @@ -86,9 +81,10 @@ let ProfileHeaderShell = ({
const modui = moderation.ui('avatar')
const avatar = profile.avatar
if (avatar && !(modui.blur && modui.noOverride)) {
const aviHandle = aviRef.current
runOnUI(() => {
'worklet'
const rect = measure(aviRef)
const rect = measureHandle(aviHandle)
runOnJS(_openLightbox)(avatar, rect)
})()
}
Expand Down Expand Up @@ -170,14 +166,14 @@ let ProfileHeaderShell = ({
styles.avi,
profile.associated?.labeler && styles.aviLabeler,
]}>
<Animated.View ref={aviRef} collapsable={false}>
<View ref={aviRef} collapsable={false}>
<UserAvatar
type={profile.associated?.labeler ? 'labeler' : 'user'}
size={90}
avatar={profile.avatar}
moderation={moderation.ui('avatar')}
/>
</Animated.View>
</View>
</View>
</TouchableWithoutFeedback>
</GrowableAvatar>
Expand Down
18 changes: 7 additions & 11 deletions src/view/com/profile/ProfileSubpageHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,13 @@
import React from 'react'
import {Pressable, StyleSheet, View} from 'react-native'
import Animated, {
measure,
MeasuredDimensions,
runOnJS,
runOnUI,
useAnimatedRef,
} from 'react-native-reanimated'
import {MeasuredDimensions, runOnJS, runOnUI} from 'react-native-reanimated'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useNavigation} from '@react-navigation/native'

import {BACK_HITSLOP} from '#/lib/constants'
import {measureHandle, useHandleRef} from '#/lib/hooks/useHandleRef'
import {usePalette} from '#/lib/hooks/usePalette'
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
import {makeProfileLink} from '#/lib/routes/links'
Expand Down Expand Up @@ -60,7 +55,7 @@ export function ProfileSubpageHeader({
const {openLightbox} = useLightboxControls()
const pal = usePalette('default')
const canGoBack = navigation.canGoBack()
const aviRef = useAnimatedRef()
const aviRef = useHandleRef()

const onPressBack = React.useCallback(() => {
if (navigation.canGoBack()) {
Expand Down Expand Up @@ -101,9 +96,10 @@ export function ProfileSubpageHeader({
if (
avatar // TODO && !(view.moderation.avatar.blur && view.moderation.avatar.noOverride)
) {
const aviHandle = aviRef.current
runOnUI(() => {
'worklet'
const rect = measure(aviRef)
const rect = measureHandle(aviHandle)
runOnJS(_openLightbox)(avatar, rect)
})()
}
Expand Down Expand Up @@ -155,7 +151,7 @@ export function ProfileSubpageHeader({
paddingBottom: 6,
paddingHorizontal: isMobile ? 12 : 14,
}}>
<Animated.View ref={aviRef} collapsable={false}>
<View ref={aviRef} collapsable={false}>
<Pressable
testID="headerAviButton"
onPress={onPressAvi}
Expand All @@ -169,7 +165,7 @@ export function ProfileSubpageHeader({
<UserAvatar type={avatarType} size={58} avatar={avatar} />
)}
</Pressable>
</Animated.View>
</View>
<View style={{flex: 1}}>
{isLoading ? (
<LoadingPlaceholder
Expand Down
13 changes: 5 additions & 8 deletions src/view/com/util/images/AutoSizedImage.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import React, {useRef} from 'react'
import {DimensionValue, Pressable, View} from 'react-native'
import Animated, {AnimatedRef, useAnimatedRef} from 'react-native-reanimated'
import {Image} from 'expo-image'
import {AppBskyEmbedImages} from '@atproto/api'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'

import {HandleRef, useHandleRef} from '#/lib/hooks/useHandleRef'
import type {Dimensions} from '#/lib/media/types'
import {isNative} from '#/platform/detection'
import {useLargeAltBadgeEnabled} from '#/state/preferences/large-alt-badge'
Expand Down Expand Up @@ -68,17 +68,14 @@ export function AutoSizedImage({
image: AppBskyEmbedImages.ViewImage
crop?: 'none' | 'square' | 'constrained'
hideBadge?: boolean
onPress?: (
containerRef: AnimatedRef<React.Component<{}, {}, any>>,
fetchedDims: Dimensions | null,
) => void
onPress?: (containerRef: HandleRef, fetchedDims: Dimensions | null) => void
onLongPress?: () => void
onPressIn?: () => void
}) {
const t = useTheme()
const {_} = useLingui()
const largeAlt = useLargeAltBadgeEnabled()
const containerRef = useAnimatedRef()
const containerRef = useHandleRef()
const fetchedDimsRef = useRef<{width: number; height: number} | null>(null)

let aspectRatio: number | undefined
Expand Down Expand Up @@ -109,7 +106,7 @@ export function AutoSizedImage({
const hasAlt = !!image.alt

const contents = (
<Animated.View ref={containerRef} collapsable={false} style={{flex: 1}}>
<View ref={containerRef} collapsable={false} style={{flex: 1}}>
<Image
style={[a.w_full, a.h_full]}
source={image.thumb}
Expand Down Expand Up @@ -188,7 +185,7 @@ export function AutoSizedImage({
)}
</View>
) : null}
</Animated.View>
</View>
)

if (cropDisabled) {
Expand Down
13 changes: 5 additions & 8 deletions src/view/com/util/images/Gallery.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import React from 'react'
import {Pressable, StyleProp, View, ViewStyle} from 'react-native'
import Animated, {AnimatedRef} from 'react-native-reanimated'
import {Image, ImageStyle} from 'expo-image'
import {AppBskyEmbedImages} from '@atproto/api'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'

import {HandleRef} from '#/lib/hooks/useHandleRef'
import {Dimensions} from '#/lib/media/types'
import {useLargeAltBadgeEnabled} from '#/state/preferences/large-alt-badge'
import {PostEmbedViewContext} from '#/view/com/util/post-embeds/types'
Expand All @@ -20,15 +20,15 @@ interface Props {
index: number
onPress?: (
index: number,
containerRefs: AnimatedRef<React.Component<{}, {}, any>>[],
containerRefs: HandleRef[],
fetchedDims: (Dimensions | null)[],
) => void
onLongPress?: EventFunction
onPressIn?: EventFunction
imageStyle?: StyleProp<ImageStyle>
viewContext?: PostEmbedViewContext
insetBorderStyle?: StyleProp<ViewStyle>
containerRefs: AnimatedRef<React.Component<{}, {}, any>>[]
containerRefs: HandleRef[]
thumbDimsRef: React.MutableRefObject<(Dimensions | null)[]>
}

Expand All @@ -52,10 +52,7 @@ export function GalleryItem({
const hideBadges =
viewContext === PostEmbedViewContext.FeedEmbedRecordWithMedia
return (
<Animated.View
style={a.flex_1}
ref={containerRefs[index]}
collapsable={false}>
<View style={a.flex_1} ref={containerRefs[index]} collapsable={false}>
<Pressable
onPress={
onPress
Expand Down Expand Up @@ -118,6 +115,6 @@ export function GalleryItem({
</Text>
</View>
) : null}
</Animated.View>
</View>
)
}
14 changes: 7 additions & 7 deletions src/view/com/util/images/ImageLayoutGrid.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import React from 'react'
import {StyleProp, StyleSheet, View, ViewStyle} from 'react-native'
import {AnimatedRef, useAnimatedRef} from 'react-native-reanimated'
import {AppBskyEmbedImages} from '@atproto/api'

import {HandleRef, useHandleRef} from '#/lib/hooks/useHandleRef'
import {PostEmbedViewContext} from '#/view/com/util/post-embeds/types'
import {atoms as a, useBreakpoints} from '#/alf'
import {Dimensions} from '../../lightbox/ImageViewing/@types'
Expand All @@ -12,7 +12,7 @@ interface ImageLayoutGridProps {
images: AppBskyEmbedImages.ViewImage[]
onPress?: (
index: number,
containerRefs: AnimatedRef<React.Component<{}, {}, any>>[],
containerRefs: HandleRef[],
fetchedDims: (Dimensions | null)[],
) => void
onLongPress?: (index: number) => void
Expand Down Expand Up @@ -43,7 +43,7 @@ interface ImageLayoutGridInnerProps {
images: AppBskyEmbedImages.ViewImage[]
onPress?: (
index: number,
containerRefs: AnimatedRef<React.Component<{}, {}, any>>[],
containerRefs: HandleRef[],
fetchedDims: (Dimensions | null)[],
) => void
onLongPress?: (index: number) => void
Expand All @@ -56,10 +56,10 @@ function ImageLayoutGridInner(props: ImageLayoutGridInnerProps) {
const gap = props.gap
const count = props.images.length

const containerRef1 = useAnimatedRef()
const containerRef2 = useAnimatedRef()
const containerRef3 = useAnimatedRef()
const containerRef4 = useAnimatedRef()
const containerRef1 = useHandleRef()
const containerRef2 = useHandleRef()
const containerRef3 = useHandleRef()
const containerRef4 = useHandleRef()
const thumbDimsRef = React.useRef<(Dimensions | null)[]>([])

switch (count) {
Expand Down
14 changes: 5 additions & 9 deletions src/view/com/util/post-embeds/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,7 @@ import {
View,
ViewStyle,
} from 'react-native'
import {
AnimatedRef,
measure,
MeasuredDimensions,
runOnJS,
runOnUI,
} from 'react-native-reanimated'
import {MeasuredDimensions, runOnJS, runOnUI} from 'react-native-reanimated'
import {Image} from 'expo-image'
import {
AppBskyEmbedExternal,
Expand All @@ -27,6 +21,7 @@ import {
ModerationDecision,
} from '@atproto/api'

import {HandleRef, measureHandle} from '#/lib/hooks/useHandleRef'
import {usePalette} from '#/lib/hooks/usePalette'
import {useLightboxControls} from '#/state/lightbox'
import {useModerationOpts} from '#/state/preferences/moderation-opts'
Expand Down Expand Up @@ -163,12 +158,13 @@ export function PostEmbeds({
}
const onPress = (
index: number,
refs: AnimatedRef<React.Component<{}, {}, any>>[],
refs: HandleRef[],
fetchedDims: (Dimensions | null)[],
) => {
const handles = refs.map(r => r.current)
runOnUI(() => {
'worklet'
const rects = refs.map(ref => (ref ? measure(ref) : null))
const rects = handles.map(measureHandle)
runOnJS(_openLightbox)(index, rects, fetchedDims)
})()
}
Expand Down

0 comments on commit 7b6c182

Please sign in to comment.