Skip to content
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

Scroll on Demand FlashList - (Fullscreen) #873

Open
1 task
sunnyysetia opened this issue Jul 12, 2023 · 1 comment
Open
1 task

Scroll on Demand FlashList - (Fullscreen) #873

sunnyysetia opened this issue Jul 12, 2023 · 1 comment
Labels
bug Something isn't working

Comments

@sunnyysetia
Copy link

Current behavior

I am attempting to make a full-screen FlashList that is scrollable by clicking a button. I was doing this with a FlatList. However, I was facing some performance issues, so I would like to switch to a FlashList. However, the scroll button doesn't seem to work.

It will work on the first scroll, but after that no longer works.

Expected behavior

Upon clicking the scroll button it should scroll for ever item in the list.

To Reproduce

The following is a minimal example I wrote for this issue. Any help with this would be highly appreciated.

import { Dimensions, Text, TouchableOpacity, View } from "react-native";
import React, { useRef, useState } from "react";

import { FlashList } from "@shopify/flash-list";

const data = [
  { background: "#8D39F1" },
  { background: "#F44336" },
  { background: "#F06595" },
  { background: "#3F51B5" },
  { background: "#00BCD4" },
];

const ActivityScreen = () => {
  const flashListRef = useRef(null);
  const [currentIndex, setCurrentIndex] = useState(0);

  const scroll = async () => {
    if (currentIndex == 4) {
      return;
    }
    if (flashListRef.current) {
      flashListRef.current.scrollToIndex({
        index: currentIndex + 1,
        animated: true,
      });
    }
    setCurrentIndex(currentIndex + 1);
  };

  const renderItem = ({ item }) => {
    const { height, width } = Dimensions.get("window");
    const windowHeight = height;
    const windowWidth = width;
    return (
      <View
        className="items-center justify-center"
        style={{
          height: windowHeight,
          width: windowWidth,
          backgroundColor: item.background,
        }}
      >
        <Text className="text-white">{item.background}</Text>
        <TouchableOpacity onPress={scroll}>
          <Text className="text-white">Scroll</Text>
        </TouchableOpacity>
      </View>
    );
  };
  return (
    <View className="flex-1 bg-[#121212]">
      <FlashList
        data={data}
        renderItem={renderItem}
        keyExtractor={(item) => item.background}
        ref={flashListRef}
        scrollEnabled={false}
        estimatedItemSize={816}
      />
    </View>
  );
};

export default ActivityScreen;

Platform:

  • [ X] iOS
  • Android

Environment

x.y.z

@sunnyysetia sunnyysetia added the bug Something isn't working label Jul 12, 2023
@suleymanbariseser
Copy link

FlashList calls renderItem only when data or extraData prop changes. While your component does not change any of these props, your renderItem function will not be updated. This means it will still use old reference of scroll function. To see what i mean, try to add a console.log(currentIndex) at the beginning of scroll function. It will always print 0. Therefore, you should find another way to access latest scroll reference.

1. Option

Pass your scroll function into renderItem with extraData. In this case, FlashList will update your reference every time.

const renderItem = ({item, extraData: {scrollFn}}) => {
  return <Pressable onPress={scrollFn}>...</Pressable>
}

return  <FlashList
     data={data}
     renderItem={renderItem}
     keyExtractor={(item) => item.background}
     ref={flashListRef}
     extraData={{ scrollFn: scroll }}
     scrollEnabled={false}
     estimatedItemSize={816}
/>

If you will prefer to use this approach then you should wrap your scroll function with useCallback. Otherwise, you will have performance issues because flashlist will update your items every time.

2. Option

You can put all your logic between setCurrentIndex so scroll function will have the latest currentIndex data. React useState setters are not reactive.

const scroll = () => {
  setCurrentIndex((index) => {
    if (flashListRef.current) {
      flashListRef.current.scrollToIndex({
        index: index + 1,
        animated: true,
      });
    }
    return index + 1;
  });
}

3. Option (best)

While you do not need reactiveness for the scroll, you can make your logic non reactive. You can read more about this solution here. useEffectEvent is not released yet but you can use useEventCallback which will do the job for you.

import useEventCallback from 'use-event-callback';

const scroll = useEventCallback(() => {
  if (currentIndex == 4) {
    return;
  }
  if (flashListRef.current) {
    flashListRef.current.scrollToIndex({
      index: currentIndex + 1,
      animated: true,
    });
  }
  setCurrentIndex(currentIndex + 1);
})

You can check the solutions in this snack

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants