-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
fix: useScrollViewOffset not working after delayed ref initialization #7612
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
MatiPl01
merged 5 commits into
main
from
@matipl01/fix-useScrollViewOffset-for-delayed-ref-initialization
Jun 4, 2025
Merged
fix: useScrollViewOffset not working after delayed ref initialization #7612
MatiPl01
merged 5 commits into
main
from
@matipl01/fix-useScrollViewOffset-for-delayed-ref-initialization
Jun 4, 2025
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
MatiPl01
commented
Jun 2, 2025
tjzel
requested changes
Jun 3, 2025
patrycjakalinska
approved these changes
Jun 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've tested it and it works okay so I'm going with approve. However I'd feel safer with merge after @tjzel approval as this refactor is not neccessarily in my field 🪨
tjzel
approved these changes
Jun 4, 2025
github-merge-queue bot
pushed a commit
that referenced
this pull request
Jun 4, 2025
## Summary This PR removes duplicate code between the web and the native implementation of the `useAnimatedRef` hook as asked in #7612 (comment) ## Test plan Copy-paste the following code snippet and see how it works. <details> <summary>Code snippet</summary> ```tsx import React, { useState } from 'react'; import { Button, ScrollView, StyleSheet, Text, View } from 'react-native'; import type Animated from 'react-native-reanimated'; import { useAnimatedReaction, useAnimatedRef, useScrollViewOffset, } from 'react-native-reanimated'; export default function EmptyExample() { const [shouldRender, setShouldRender] = useState(false); const scrollRef = useAnimatedRef<Animated.ScrollView>(); const scrollOffset = useScrollViewOffset(scrollRef); useAnimatedReaction( () => scrollOffset.value, (value) => { console.log('scrollOffset', value); } ); return ( <View style={styles.container}> <Text style={styles.text}> ScrollView is {shouldRender ? 'rendered' : 'not rendered'} </Text> <Button title={shouldRender ? 'Remove ScrollView' : 'Render ScrollView'} onPress={() => setShouldRender(!shouldRender)} /> {shouldRender && ( <ScrollView ref={scrollRef} scrollEventThrottle={1000 / 60} style={styles.scrollView}> {Array.from({ length: 20 }).map((_, index) => ( <View key={index} style={styles.item}> <Text style={styles.itemText}>Scroll Item {index + 1}</Text> </View> ))} </ScrollView> )} </View> ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#fff', padding: 20, }, text: { fontSize: 16, marginBottom: 10, }, scrollView: { flex: 1, marginTop: 20, }, item: { height: 100, backgroundColor: '#f0f0f0', marginVertical: 8, borderRadius: 8, justifyContent: 'center', alignItems: 'center', }, itemText: { fontSize: 16, color: '#333', }, }); ``` </details>
This was referenced Jun 25, 2025
MatiPl01
added a commit
that referenced
this pull request
Jun 26, 2025
…#7612) This PR fixes `useScrollViewOffset` hook not updating the returned SharedValue if the scrollable to which the ref is attached is rendered with some delay. The previous implementation didn't work because the `animatedRef` was uninitialized when the `useEffect` hook in the `useScrollViewOffset` was called if the ref wasn't passed to any component. The `useEffect` was not triggered after the ref was passed to the component later on, even though (at least as I understand) the `animatedRef?.current` in the `useEffect` dependencies changed, because this change happened after hook dependencies were recalculated. Since the code od the component executes from the top to the bottom during render, the `useEffect` hook dependencies array was created before the `animatedRef.current` was set in the function created within the `useAnimatedRef` hook (it is set as soon as the ref function is called with the exiting component argument, which happened after `useEffect` hook dependencies were recalculated). | Before | After | |-|-| | <video src="https://github.com/user-attachments/assets/16b2d7dd-ec29-45be-ab7e-44007e922204" /> | <video src="https://github.com/user-attachments/assets/9e6f4475-4f66-4b46-a42b-2322a3cdb301" /> | | Before | After | |-|-| | <video src="https://github.com/user-attachments/assets/4e8f02af-5aee-498a-a738-a674db062bbc" /> | <video src="https://github.com/user-attachments/assets/cd0965da-7e2c-4281-ac3b-f3f58c97e153" /> | Copy-paste the following code snippet and see how it works. <details> <summary>Code snippet</summary> ```tsx import React, { useState } from 'react'; import { Button, ScrollView, StyleSheet, Text, View } from 'react-native'; import type Animated from 'react-native-reanimated'; import { useAnimatedReaction, useAnimatedRef, useScrollViewOffset, } from 'react-native-reanimated'; export default function EmptyExample() { const [shouldRender, setShouldRender] = useState(false); const scrollRef = useAnimatedRef<Animated.ScrollView>(); const scrollOffset = useScrollViewOffset(scrollRef); useAnimatedReaction( () => scrollOffset.value, (value) => { console.log('scrollOffset', value); } ); return ( <View style={styles.container}> <Text style={styles.text}> ScrollView is {shouldRender ? 'rendered' : 'not rendered'} </Text> <Button title={shouldRender ? 'Remove ScrollView' : 'Render ScrollView'} onPress={() => setShouldRender(!shouldRender)} /> {shouldRender && ( <ScrollView ref={scrollRef} scrollEventThrottle={1000 / 60} style={styles.scrollView}> {Array.from({ length: 20 }).map((_, index) => ( <View key={index} style={styles.item}> <Text style={styles.itemText}>Scroll Item {index + 1}</Text> </View> ))} </ScrollView> )} </View> ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#fff', padding: 20, }, text: { fontSize: 16, marginBottom: 10, }, scrollView: { flex: 1, marginTop: 20, }, item: { height: 100, backgroundColor: '#f0f0f0', marginVertical: 8, borderRadius: 8, justifyContent: 'center', alignItems: 'center', }, itemText: { fontSize: 16, color: '#333', }, }); ``` </details> I also checked if the following examples in the example app still work (on mobile and web): - useAnimatedScrollHandler - scrollTo - useScrollViewOffset
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Summary
This PR fixes
useScrollViewOffset
hook not updating the returned SharedValue if the scrollable to which the ref is attached is rendered with some delay.The previous implementation didn't work because the
animatedRef
was uninitialized when theuseEffect
hook in theuseScrollViewOffset
was called if the ref wasn't passed to any component. TheuseEffect
was not triggered after the ref was passed to the component later on, even though (at least as I understand) theanimatedRef?.current
in theuseEffect
dependencies changed, because this change happened after hook dependencies were recalculated.Since the code od the component executes from the top to the bottom during render, the
useEffect
hook dependencies array was created before theanimatedRef.current
was set in the function created within theuseAnimatedRef
hook (it is set as soon as the ref function is called with the exiting component argument, which happened afteruseEffect
hook dependencies were recalculated).Example recordings
Mobile
Screen.Recording.2025-06-02.at.18.30.20.mp4
Screen.Recording.2025-06-02.at.18.29.37.mp4
Web
Screen.Recording.2025-06-02.at.18.31.53.mp4
Screen.Recording.2025-06-02.at.18.33.03.mp4
Test plan
Copy-paste the following code snippet and see how it works.
Code snippet
I also checked if the following examples in the example app still work (on mobile and web):