React-native-reanimated: Is it possible to do callback with the whole event rather than single value from nativeEvent?

Created on 6 Jun 2020  ·  12Comments  ·  Source: software-mansion/react-native-reanimated

Description

Hi guys!
I'm trying to use react-native-reanimated with recyclerlistview. And I need to pass my own implement of ScrollView to the RecyclerListView (This is pretty similar to pass renderScrollComponent to Flatlist). There are examples that using Animated from vanilla react-native. However, this needs passing the whole event argument to the onScroll function which would be looked like:

<Animated.ScrollView
  {...this.props}
  onScroll={
    Animated.event(
      [this.props.animatedEvent], // <- typeof this props would be Animated.Event
      { listener: this.props.onScroll }
    )
  }
/>

I tried to use callback node, but here I need to pass the whole event rather than something like contentOffset.y.

I know that it won't be possible because the nativeEvent is not an Animated value, but I found there is a hook called useAnimatedScrollHandler in v2, and the argument of this hook is a worklet. So maybe this would be possible in react-native-reanimated v2?

Package versions

  • React: 16.13.1
  • React Native: 0.62.2
  • React Native Reanimated: 1.9.0
❓Question 🏠 Reanimated2

All 12 comments

Do you need callback to ja or you need an animation based on it's value? Could you describe your use case better? I'm not sure I follow.

@terrysahaidak thanks for your reply, and I think I need to give both Animated.event and props.onScroll to Animated.ScrollView.

update:
props.onScroll was overridden by RLV internally, I need to pass an event: ScrollEvent argument to this function.
But I don't know how to get the event if I pass an Animated.event to Animated.ScrollView.

👇 and this is the code that handling this with Animated from react-native:

onScroll={
    Animated.event(
      [
        // my Animated.event heree
      ],
      { listener: this.props.onScroll }
    )
  }

After reading the code of RecyclerView I can see it depends on that on scroll callback it passes to externalScrollView component.

Let me figure something out and I'll get back to you.

you can do something similar to this as a workaround https://github.com/software-mansion/react-native-reanimated/issues/67#issuecomment-545955795

How would one do this using the v2 ? I have a similar issue with FlatList, Animated.ScrollView and Reanimated v2 (like this one : https://github.com/software-mansion/react-native-reanimated/issues/457 but with Reanimated v2)

How would one do this using the v2 ? I have a similar issue with FlatList, Animated.ScrollView and Reanimated v2 (like this one : #457 but with Reanimated v2)

Did you found any answers? I am facing the same issue 😢

How would one do this using the v2 ? I have a similar issue with FlatList, Animated.ScrollView and Reanimated v2 (like this one : #457 but with Reanimated v2)

Did you found any answers? I am facing the same issue 😢

Well, instead I'm now using this custom component :

import React, { forwardRef } from 'react';
import Animated from 'react-native-reanimated';
import { FlatList } from 'react-native';

const FlatListWithEventThrottle = forwardRef((props, ref) => (
  <FlatList
    {...props}
    scrollEventThrottle={16}
    // @ts-ignore
    ref={ref}
  />
));

export const AnimatedFlatList: typeof FlatList = Animated.createAnimatedComponent(
  FlatListWithEventThrottle
);

How would one do this using the v2 ? I have a similar issue with FlatList, Animated.ScrollView and Reanimated v2 (like this one : #457 but with Reanimated v2)

Did you found any answers? I am facing the same issue 😢

Well, instead I'm now using this custom component :

import React, { forwardRef } from 'react';
import Animated from 'react-native-reanimated';
import { FlatList } from 'react-native';

const FlatListWithEventThrottle = forwardRef((props, ref) => (
  <FlatList
    {...props}
    scrollEventThrottle={16}
    // @ts-ignore
    ref={ref}
  />
));

export const AnimatedFlatList: typeof FlatList = Animated.createAnimatedComponent(
  FlatListWithEventThrottle
);

Thansk for the answer - yet no luck with that ..

@jakubgrzelak could you post more info? What version of RN, Reanimated, code that reproduces the issue. Thanks

RN version = 0.62.2
"react-native-reanimated": "^2.0.0-alpha.7",
@terrysahaidak

this is a functional component ...

const heightValue = useSharedValue(0)
  const scrollHandler = useAnimatedScrollHandler((event) => {
    console.log('I am here');
    heightValue.value = event.contentOffset.y;
  });

  const height = useDerivedValue(() => {
    return interpolate(heightValue.value, [0, 430], [430, 170], Extrapolate.CLAMP);
  });

  const animatedStyle = useAnimatedStyle(() => {
    return {
      width: heightValue.value + 100 // for testing too see if anything happens on scroll
    }
  })

  return (
    <>
      <EventListHeader scrollToTop={scrollToTop} y={heightValue.value} height={height} />
      <AnimatedFlatList
        data={data}
        extraData={extraData}
        automaticallyAdjustContentInsets={true}
        removeClippedSubviews={true}
        contentInset={{ bottom: isNotchPresent ? 74 : 50 }}
        // style={{ paddingBottom: flatListPadding }}
        style={animatedStyle}
        showsVerticalScrollIndicator={false}
        refreshControl={
          <RefreshControl
            refreshing={isLoading || isListRefreshing}
            onRefresh={onRefresh}
          />
        }
        renderItem={({ item, index }) => (
          <Event
            // deletePetEvent={deletePetEvent}
            data={item}
            last={index + 1 === dataLength}
            scrollToTop={scrollToTop}
          />
        )}
        renderScrollComponent={props => {
          return (
            <Animated.ScrollView
              {...props}
              onScroll={scrollHandler}
            />
          );
        }}
        onScroll={scrollHandler}
        keyExtractor={(item, index) => index.toString()}
        ListFooterComponent={renderFooter}
        ref={flatListRef}
        // onScroll={scrollHandler}
        bounces={false}
        onEndReachedThreshold={0.5}
        onEndReached={onEndReach}
        onMomentumScrollEnd={() => {
          callOnScrollEnd && handleLoadMore();
          callOnScrollEnd = false;
        }}
      />
    </>
  );

I don't know what did you try to achieve with that example, but here is the working one.

Some highlights to remember:

  • Passing animated styles to FlatList itself isn't a good idea (you may reduce the scroll area)
  • If you want to pass a shared value to some child component you need to pass it as is, so not the sharedValue.value, because as soon as you access .value – it's just a regular variable
  • If you want to animate component - you should pass the result of useAnimatedStyle to styles directly.
  • make sure to pass scrollEventThrottle to flat list so scroll event won't be laggy
import React from 'react';
import { FlatList } from 'react-native';
import Animated, {
  Extrapolate,
  interpolate,
  useAnimatedScrollHandler,
  useAnimatedStyle,
  useDerivedValue,
  useSharedValue,
} from 'react-native-reanimated';

const AnimatedFlatList = Animated.createAnimatedComponent(FlatList);

const data = Array.from({ length: 10 }).map((_, index) => ({
  id: index,
}));

function EventListHeader({ y, height }) {
  const animatedStyle = useAnimatedStyle(() => ({
    transform: [{ translateY: y.value }],
  }));

  return (
    <Animated.View
      style={[
        {
          height: 100,
          width: '100%',
          backgroundColor: 'green',
        },
        animatedStyle,
      ]}
    ></Animated.View>
  );
}

function Event() {
  return (
    <Animated.View
      style={{
        width: '100%',
        height: 150,
        backgroundColor: 'red',
        borderBottomColor: 'green',
        borderBottomWidth: 1,
      }}
    ></Animated.View>
  );
}

export default function Test() {
  const heightValue = useSharedValue(0);

  const scrollHandler = useAnimatedScrollHandler((event) => {
    console.log('I am here');
    heightValue.value = event.contentOffset.y;
  });

  const height = useDerivedValue(() => {
    return interpolate(
      heightValue.value,
      [0, 430],
      [430, 170],
      Extrapolate.CLAMP,
    );
  });

  return (
    <>
      <EventListHeader y={heightValue} height={height} />
      <AnimatedFlatList
        data={data}
        // extraData={extraData}
        automaticallyAdjustContentInsets={true}
        removeClippedSubviews={true}
        contentInset={{ bottom: 50 }}
        // style={{ paddingBottom: flatListPadding }}
        // style={animatedStyle}
        showsVerticalScrollIndicator={false}
        renderItem={({ item, index }) => (
          <Event
          // deletePetEvent={deletePetEvent}
          // data={item}
          // last={index + 1 === dataLength}
          // scrollToTop={scrollToTop}
          />
        )}
        scrollEventThrottle={1}
        onScroll={scrollHandler}
        keyExtractor={(item, index) => index.toString()}
        // ListFooterComponent={renderFooter}
        // ref={flatListRef}
        // onScroll={scrollHandler}
        bounces={false}
        onEndReachedThreshold={0.5}
        // onEndReached={onEndReach}
        // onMomentumScrollEnd={() => {
        //   callOnScrollEnd && handleLoadMore();
        //   callOnScrollEnd = false;
        // }}
      />
    </>
  );
}

Also, in the future, please provide full code :)

Huge thanks to @terrysahaidak for helping folks with Reanimated problems.

Reanimated 1 likely won't support this behavior and Terry demonstrated that it can be done with v2, so I'm considering this as resolved. If you have any further concerns don't hesitate to reply.

Was this page helpful?
0 / 5 - 0 ratings