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

ViewableItems not working with FlashList #566

Closed
brunolcarlos opened this issue Aug 16, 2022 · 26 comments
Closed

ViewableItems not working with FlashList #566

brunolcarlos opened this issue Aug 16, 2022 · 26 comments
Labels
bug Something isn't working

Comments

@brunolcarlos
Copy link

brunolcarlos commented Aug 16, 2022

With FlatList and react-native-big-list the ViewableItems works fine but with FlashList does't works

When the items change the FlashList it's not passing the new state to Component inside of the renderItem

How can I fix It ?

This is a Android APP expo 46 and Expo GO

const onViewRef = React.useRef(({ viewableItems, changed }) => {
    if (viewableItems && viewableItems.length > 0) {
      setItemState(viewableItems[0]);
    }
  });

  const viewConfigRef = React.useRef({
    viewAreaCoveragePercentThreshold: 50,
  });

 const keyExtractor = useCallback(
    (item, index) => `${item.hash}${index}${item.post_id}`,
    []
  );

  const renderItemFinal = ({ item, index }) => {
    if (item?.post_type == "video") {
      return (
        <Video
          patrocinado={item?.patrocinado}
          index={index * page}
          userStorage={userStorage}
          postUnlike={postUnlike}
          postLike={postLike}
          onViewRef={
            itemState?.item?.post_id == item?.post_id && itemState?.isViewable
              ? true
              : false
          }
          item={item}
        />
      );
    }
  };

 const itemRender= useMemo(() => renderItemFinal, [itemState]);


 <FlashList
    refreshControl={refreshingjsx}
    data={videos}
    onEndReachedThreshold={0.2}
    onEndReached={reachedJsx}
    renderItem={itemRender}
    onViewableItemsChanged={onViewRef.current}
    viewabilityConfig={viewConfigRef.current}
    keyExtractor={keyExtractor}
    snapToAlignment={"start"}
    decelerationRate={"fast"}
    snapToInterval={win.height}
    itemHeight={win.height}
    estimatedItemSize={100}
/>

@brunolcarlos brunolcarlos added the bug Something isn't working label Aug 16, 2022
@fortmarek
Copy link
Contributor

Hey @brunolcarlos 👋

Could you create a reproducible sample (either on GitHub or on expo's snack) and add steps how to reproduce the issue, detailing the exact specifics of what you are seeing? The code snippet you posted does not include the complete code.

@brunolcarlos
Copy link
Author

Hey @brunolcarlos 👋

Could you create a reproducible sample (either on GitHub or on expo's snack) and add steps how to reproduce the issue, detailing the exact specifics of what you are seeing? The code snippet you posted does not include the complete code.

HI formarek, it's not a message error ou compiling error exactly.
This is a state error, when I use the original FlatList I change the state (play) and pass to my child to play or stop the videos, with the FlashList the state inside of my child does't change, if I use with images will works very well but videos I need to play and stop.
If I put the FlatList everything works but with FlashList the videos dont receive the new state passed on props.

@naqvitalha
Copy link
Collaborator

@brunolcarlos the incomplete code is making debugging this harder but if I have to guess, it's probably because FlashList doesn't update all the items unless your data or extraData change.
If there's additional prop rendering depends on, pass it as extraData.

@brunolcarlos
Copy link
Author

@brunolcarlos the incomplete code is making debugging this harder but if I have to guess, it's probably because FlashList doesn't update all the items unless your data or extraData change.
If there's additional prop rendering depends on, pass it as extraData.

Hi, I don't need to update the array list, I only update the child when is on viewport and pass a prop to child with true or false.
This app is like the Instagram reels, when the video is on viewport I play the video and stop when is not on viewport.

The Component Video doesn't receive this new information with the flashlist but receive with FlatList.

@naqvitalha
Copy link
Collaborator

@brunolcarlos I got that. Did you try updating extraData when you want the list to render?
You can also set it to extraData={{}} for checking quickly. Updating on every render, unless necessary, can cause perf problems. But you can quickly try this way.

@brunolcarlos
Copy link
Author

brunolcarlos commented Aug 18, 2022

Hi @naqvitalha , I'm not sure if you understood whats is happening.
I dont need change the array list, I need to play and stop videos while they move on my feed.

Using viewableItems I can get them

const onViewRef = React.useRef(({ viewableItems, changed }) => {
    if (viewableItems && viewableItems.length > 0) {
      setItemState(viewableItems[0]);
    }
  });
  const viewConfigRef = React.useRef({
    viewAreaCoveragePercentThreshold: 50,
  });

AND here I pass the information to my Video Component

const renderItemFinal = ({ item, index }) => {
    if (item?.post_type == "video") {
      return (
        <Video
          patrocinado={item?.patrocinado}
          index={index * page}
          userStorage={userStorage}
          postUnlike={postUnlike}
          postLike={postLike}

         /*###############################################*/

          onViewRef={
            itemState?.item?.post_id == item?.post_id && itemState?.isViewable 
              ? true
              : false
          }

        /*###############################################t*/


          item={item}
        />
      );
    }
  };

ON this line

onViewRef={
        itemState?.item?.post_id == item?.post_id && itemState?.isViewable
          ? true
          : false
 }

With FlashList its not working but with FlatList works how I expected

With FlashList the rendered components do not receive from props the new state

@fortmarek
Copy link
Contributor

The issue with your solution is that the item does not re-render when renderItem memoized value changes. That's why @naqvitalha recommended using extraData when your itemState changes as your list needs to re-render (and that's not happening as of now).

@chrisedington
Copy link

chrisedington commented Aug 31, 2022

I had a similar issue and tried this out by using extraData. but when it comes to updating any state I get an infinite rerender. Any suggestions on how I can perhaps overcome that?

@brunolcarlos did you manage to find a solution?

@brunolcarlos
Copy link
Author

I had a similar issue and tried this out by using extraData. but when it comes to updating any state I get an infinite rerender. Any suggestions on how I can perhaps overcome that?

@brunolcarlos did you manage to find a solution?

Nope, I moved back to FlatList.

@fortmarek
Copy link
Contributor

I had a similar issue and tried this out by using extraData. but when it comes to updating any state I get an infinite rerender. Any suggestions on how I can perhaps overcome that?

@chrisedington happy to help if you provide a sample

@hirbod
Copy link
Contributor

hirbod commented Sep 11, 2022

I can tell you that Viewability works absolutely fine with FlashList. I am using it in production apps on huge lists. The issues I've came across have been the use of state inside my children and the way I've kept track of my refs. You need to make sure to react to changes within useEffect when using state in your items or use a smarter way, like context to react to visibility inside your items (starting/stopping videos is exactly what I am doing)

I'd recommend you this Gist by @intergalacticspacehighway
https://gist.github.com/intergalacticspacehighway/02a36b05b2236bc750a065833b71c94a

I've ported his approach to FlashList within 5 minutes and its actually the most performant way to react to visibility changes.

@brunolcarlos
Copy link
Author

With FlatList works, with FlashList not.

@hirbod
Copy link
Contributor

hirbod commented Sep 11, 2022

As said, it does. You're doing something wrong and not sharing enough to really help

@brunolcarlos
Copy link
Author

brunolcarlos commented Sep 11, 2022 via email

@hirbod
Copy link
Contributor

hirbod commented Sep 11, 2022

Yes, because those lists you've been working with are not recycling. You need to understand that when you recycle views, you need to take care of some circumstances, like stale state (when you are working with state).

If you wan't help, setup up a repro with Snack and I can help you out.

@brunolcarlos
Copy link
Author

I'm changing the state on every component and it's not working when you need change the state after the component is mounted.
It's not working with videos and autoplay

@hirbod
Copy link
Contributor

hirbod commented Sep 11, 2022

I'll give up. Have fun with FlatList.

@naqvitalha
Copy link
Collaborator

I'm closing this due to the lack of a sample that we can work with. If you do decide to create one please provide it and we'll look into it.

@bfricka
Copy link

bfricka commented Sep 27, 2022

I can tell you that extraData or data have no effect on this issue. The handler runs fine when viewability changes due to user interaction, but if the data causes the list to re-render, and this affects viewable items, the handler does not fire.

My list re-renders fine. The item itself renders. The onViewableItemsChanged handler does not always fire. I've seen it fire sometimes, but many times it doesn't.

@hirbod
Copy link
Contributor

hirbod commented Sep 27, 2022

I can tell you that extraData or data have no effect on this issue. The handler runs fine when viewability changes due to user interaction, but if the data causes the list to re-render, and this affects viewable items, the handler does not fire.

My list re-renders fine. The item itself renders. The onViewableItemsChanged handler does not always fire. I've seen it fire sometimes, but many times it doesn't.

@naqvitalha this would explain the issue we once tried to bugfix (videos not firing play/stop when data changes)
Also ping @intergalacticspacehighway

I was able to fix this issue though, at least for my app. We changed our keyExtractor form item id to index and that worked out pretty well for us.

@eeshankeni
Copy link

I can tell you that Viewability works absolutely fine with FlashList. I am using it in production apps on huge lists. The issues I've came across have been the use of state inside my children and the way I've kept track of my refs. You need to make sure to react to changes within useEffect when using state in your items or use a smarter way, like context to react to visibility inside your items (starting/stopping videos is exactly what I am doing)

I'd recommend you this Gist by @intergalacticspacehighway
https://gist.github.com/intergalacticspacehighway/02a36b05b2236bc750a065833b71c94a

I've ported his approach to FlashList within 5 minutes and its actually the most performant way to react to visibility changes.

Could you please provide your flashlist implementation for this gist? Thanks

@brunolcarlos
Copy link
Author

The problem its not if is firing or not, the problem is because the Component still static and nothing change.
Try to create a tiktok feed and make a videos auto play, will not work

@arthurgeron-work
Copy link

Hey @brunolcarlos , a colleague showed me this issue and I wanted to give you my input on the matter, and to anyone finding this issue.
It's possible to implement a short video feed with autoplay using FlashList, here's one I did myself:

RPReplay_Final1697231525.mp4

There are some key rules when developing short video feeds, here are the most important:

  1. Never base your logic on mount/unmount, as FlashList doesn't unmount/mount components, instead, it recycles the same Component instance in "Virtual DOM" with props from a different index of your data array;
  2. When saving information about the visible component(s), avoid passing it directly to renderItem, instead, you can use some store outside the React Scope (e.g. zustand), or Flash List's extraData which FlashList itself injects into renderItem's props, allowing the method to be declared outside the React scope, which means it won't be instantiated during state changes, meaning better performance;
  3. Avoid using useRef values as props, since changes in references don't trigger a new render cycle. Instead use the methods described previously to store data, as a data storage useRef is mostly useful to access/change data within hooks like useEffect without triggering new cycles;
  4. Use viewed index + "is focused"(react-navigation) to have proper autoplay/autopause, if you also pause videos during scroll drag you'll have better results.;
  5. Make sure all state values get properly reset when main identifiers of your list item component change (e.g. id), this is crucial since FlashList recycles components;
  6. Create a very strict set of rules in your viewabilityConfig, so videos pause/play perfectly when they're supposed to.

About "Viewable Items" events:

  • It doesn't fire (as far as I remember) without a scroll event, meaning the first time the screen loads, or if you navigate back to the screen, the event won't be fired. Which means it's a good idea to keep it in a store (not a local useState) so that information can be kept until you don't need it anymore;
  • The event itself does not trigger new renders, hence storing it in a ref won't trigger new render cycles, hence why autoplay with ref wouldn't work.

I hope this helps, I can't share much code but this should show how practical and simple that implementation can be.

Viewable Config:

image

Viewable Method(stored in zustand store):

image

List Item Using that for autoplay:

image

@arthurgeron-work
Copy link

arthurgeron-work commented Oct 13, 2023

I can tell you that extraData or data have no effect on this issue. The handler runs fine when viewability changes due to user interaction, but if the data causes the list to re-render, and this affects viewable items, the handler does not fire.

My list re-renders fine. The item itself renders. The onViewableItemsChanged handler does not always fire. I've seen it fire sometimes, but many times it doesn't.

Some misconceptions here:

  • Viewable Items event only gets triggered by scroll events, so this information needs to be kept if you want to maintain position after the order of the data array changes, if you need to maintain position you'd need to use that info alongside scrollToIndex;
  • FlashList does not have a way to maintain current scroll position, when the array data changes, without being manually implemented by the dev.

@Anas-7
Copy link

Anas-7 commented Jul 5, 2024

In case anyone needs help on this issue, there is a solution that worked for me. In my case, I was sending a prop and a callback to change its value to the item rendered in flashlist but I noticed that the value wouldn't change within the item, but it would in the component where I had the flashlist. So the solution was to trigger a re-render that would make sure the items of flashlist receive that change.

Here is a short snippet:

const [reRender, setReRender] = useState(false);
const [yourVariableorButtonClick, setYourVariableorButtonClick] = useState(something);


useEffect(() => {
   // In my case I wanted to trigger the re-render only when the value was true
  if(yourVariableorButtonClick)
     setReRender(prev => !prev);
}, [yourVariableorButtonClick]);

Then go to your Flashlist component and add the re-render variable to extra data

<FlashList
    data={data}
    extraData={reRender}
    renderItem={(item) => (
        <Component
            yourVariableorButtonClick={yourVariableorButtonClick}
            setYourVariableorButtonClick={setYourVariableorButtonClick}
            your_prop={item}
       />
    )}
/>

This let me use the viewableItemsConfig and other stuff such as passing the flashlist ref to the items and using scrollToIndex method within the item to scroll to next post, and I imagine it can be done for clicking on buttons too then.

@hirbod
Copy link
Contributor

hirbod commented Jul 6, 2024

We have a proper fix for this, Nishan will provide a PR for this next week.

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

9 participants