Skip to content

Commit

Permalink
Fix scrollTo on FlashList (#4384)
Browse files Browse the repository at this point in the history
## Summary

Fixes #4376. Analogous to #3988.

Solution: copy the code from `createAnimatedComponent.tsx` to
`useAnimatedRef.ts`


https://github.com/software-mansion/react-native-reanimated/blob/87fb9c6a1fb6b8c839b65b90ad16f574f45a1294/src/createAnimatedComponent.tsx#L289-L291

Example:

<details>
<summary>App.tsx</summary>

```tsx
import Animated, {
  runOnUI,
  scrollTo,
  useAnimatedRef,
} from 'react-native-reanimated';
import {Button, StyleSheet, Switch, Text, View} from 'react-native';
import {FlashList} from '@shopify/flash-list';

import React from 'react';

declare const _WORKLET: boolean;

const AnimatedFlashList = Animated.createAnimatedComponent(FlashList);

interface Item {
  label: string;
}

const DATA: Item[] = [...Array(100)].map((_, i) => ({label: String(i)}));

function getRandomOffset() {
  'worklet';
  return Math.random() * 2000;
}

export default function ScrollToExample() {
  const [animated, setAnimated] = React.useState(true);

  const aref = useAnimatedRef<FlashList<Item>>();

  const scrollFromJS = () => {
    console.log(_WORKLET);
    aref.current?.scrollToOffset({offset: getRandomOffset(), animated});
  };

  const scrollFromUI = () => {
    runOnUI(() => {
      'worklet';
      console.log(_WORKLET);
      scrollTo(aref, 0, getRandomOffset(), animated);
    })();
  };

  return (
    <>
      <View style={styles.buttons}>
        <Switch
          value={animated}
          onValueChange={setAnimated}
          style={styles.switch}
        />
        <Button onPress={scrollFromJS} title="Scroll from JS" />
        <Button onPress={scrollFromUI} title="Scroll from UI" />
      </View>
      <AnimatedFlashList
        // @ts-ignore createAnimatedComponent seems to break generic types
        ref={aref}
        data={DATA}
        // @ts-ignore createAnimatedComponent seems to break generic types
        renderItem={({item}: {item: Item}) => (
          <Text key={item.label} style={styles.text}>
            {item.label}
          </Text>
        )}
        estimatedItemSize={200}
      />
    </>
  );
}

const styles = StyleSheet.create({
  switch: {
    marginBottom: 10,
  },
  buttons: {
    marginTop: 80,
    marginBottom: 40,
    alignItems: 'center',
  },
  text: {
    fontSize: 50,
    textAlign: 'center',
  },
});
```

</details>

Before:

<img
src="https://user-images.githubusercontent.com/20516055/233323545-427cc3d4-4479-4b02-a24b-5a3039fb3105.png"
width="300" />

Android:


https://user-images.githubusercontent.com/20516055/233322746-637fa2a9-0e93-40ac-baa8-c3c3d2449867.mp4

iOS:


https://user-images.githubusercontent.com/20516055/233322714-20c03ade-1f15-4d76-8a5e-b4b59ebe8a73.mp4

## Test plan

1. Install FlashList in Example app with `yarn add @shopify/flash-list
&& cd ios && pod install`

2. Paste the attached code snippet into App.tsx

> **Warning**
> It looks like animated refs don't update properly on render. In this
case, pressing "Scroll from UI" button after a fast refresh does
nothing. Please reload the <kbd>r</kbd> to reload the app and check
again.
  • Loading branch information
tomekzaw authored Apr 21, 2023
1 parent 1619619 commit 690b887
Showing 1 changed file with 11 additions and 3 deletions.
14 changes: 11 additions & 3 deletions src/reanimated2/hook/useAnimatedRef.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,12 @@ import {
registerShareableMapping,
} from '../shareables';

interface ComponentRef extends Component {
getScrollableNode?: () => ComponentRef;
}

function getShareableShadowNodeFromComponent(
component: Component
component: ComponentRef
): ShadowNodeWrapper {
return getShadowNodeWrapperFromHostInstance(component);
}
Expand All @@ -19,15 +23,19 @@ const getTagValueFunction = global._IS_FABRIC
? getShareableShadowNodeFromComponent
: getTag;

export function useAnimatedRef<T extends Component>(): RefObjectFunction<T> {
export function useAnimatedRef<T extends ComponentRef>(): RefObjectFunction<T> {
const tag = useSharedValue<number | ShadowNodeWrapper | null>(-1);
const ref = useRef<RefObjectFunction<T>>();

if (!ref.current) {
const fun: RefObjectFunction<T> = <RefObjectFunction<T>>((component) => {
// enters when ref is set by attaching to a component
if (component) {
tag.value = getTagValueFunction(component);
tag.value = getTagValueFunction(
component.getScrollableNode
? component.getScrollableNode()
: component
);
fun.current = component;
}
return tag.value;
Expand Down

0 comments on commit 690b887

Please sign in to comment.