1.9 KiB
1.9 KiB
title, impact, impactDescription, tags
| title | impact | impactDescription | tags |
|---|---|---|---|
| Never Track Scroll Position in useState | HIGH | prevents render thrashing during scroll | scroll, performance, reanimated, useRef |
Never Track Scroll Position in useState
Never store scroll position in useState. Scroll events fire rapidly—state
updates cause render thrashing and dropped frames. Use a Reanimated shared value
for animations or a ref for non-reactive tracking.
Incorrect (useState causes jank):
import { useState } from 'react'
import {
ScrollView,
NativeSyntheticEvent,
NativeScrollEvent,
} from 'react-native'
function Feed() {
const [scrollY, setScrollY] = useState(0)
const onScroll = (e: NativeSyntheticEvent<NativeScrollEvent>) => {
setScrollY(e.nativeEvent.contentOffset.y) // re-renders on every frame
}
return <ScrollView onScroll={onScroll} scrollEventThrottle={16} />
}
Correct (Reanimated for animations):
import Animated, {
useSharedValue,
useAnimatedScrollHandler,
} from 'react-native-reanimated'
function Feed() {
const scrollY = useSharedValue(0)
const onScroll = useAnimatedScrollHandler({
onScroll: (e) => {
scrollY.value = e.contentOffset.y // runs on UI thread, no re-render
},
})
return (
<Animated.ScrollView
onScroll={onScroll}
// higher number has better performance, but it fires less often.
// unset this if you need higher precision over performance.
scrollEventThrottle={16}
/>
)
}
Correct (ref for non-reactive tracking):
import { useRef } from 'react'
import {
ScrollView,
NativeSyntheticEvent,
NativeScrollEvent,
} from 'react-native'
function Feed() {
const scrollY = useRef(0)
const onScroll = (e: NativeSyntheticEvent<NativeScrollEvent>) => {
scrollY.current = e.nativeEvent.contentOffset.y // no re-render
}
return <ScrollView onScroll={onScroll} scrollEventThrottle={16} />
}