react-tv-space-navigation icon indicating copy to clipboard operation
react-tv-space-navigation copied to clipboard

Weird flickering combining ScrollView, List, and Grid

Open dgocoder opened this issue 1 year ago • 10 comments

Describe the bug For whatever reason navigating down on a grid view inside of a scrollview is causing some weird flickering instead of working as expected. If I do not have it in scroll view it works fine. If i load the page and defer the grid from showing it works fine. However using a key on the grid causes this issue.

To Reproduce

const [catFilter, setCatFilter] = useState("Todas");
  const setOndemandFilter = useCallback((cat: Category) => {
    setCatFilter(cat);
  }, []);
retrun (<Page><DefaultFocus>
        <SpatialNavigationScrollView
          offsetFromStart={140}
          style={{ backgroundColor: "black" }}
        >
          <View className="flex-1 bg-black px-4 pt-[18.5px]">
            <View className="h-32">
              <Text
                style={{ fontSize: 20 }}
                className="pb-2 font-medium text-white"
              >
                Categorias
              </Text>
              <SpatialNavigationVirtualizedList
                orientation="horizontal"
                data={categoriesIndexed}
                renderItem={renderCategory}
                itemSize={178}
                numberOfRenderedItems={WINDOW_SIZE}
                numberOfItemsVisibleOnScreen={NUMBER_OF_ITEMS_VISIBLE_ON_SCREEN}
                onEndReachedThresholdItemsNumber={
                  NUMBER_OF_ITEMS_VISIBLE_ON_SCREEN
                }
              />
            </View>
            <Text
              style={{ fontSize: 20 }}
              className="py-2 font-medium text-white"
            >
              {categoryMapping[catFilter] ?? "Todas"}
            </Text>
            <SpatialNavigationVirtualizedGrid
              data={schedulePrograms}
              key={catFilter}
              style={{ backgroundColor: "#000000" }}
              renderItem={renderItem}
              itemHeight={207 * 1.075}
              numberOfColumns={NUMBER_OF_COLUMNS}
              numberOfRenderedRows={NUMBER_OF_RENDERED_ROWS}
              numberOfRowsVisibleOnScreen={NUMBER_OF_ROWS_VISIBLE_ON_SCREEN}
              onEndReachedThresholdRowsNumber={INFINITE_SCROLL_ROW_THRESHOLD}
              scrollInterval={150}
            />
          </View>
        </SpatialNavigationScrollView>
      </DefaultFocus></Page>)

Expected behavior Would expect the grid to work as normal

Screenshots https://github.com/bamlab/react-tv-space-navigation/assets/13984190/580e26a2-2b6f-4884-83c0-1fda10d88103

Version and OS

  • Library version: 3/1.2
  • React Native version: 0.73.6
  • OS [e.g. Android, web]: Android

dgocoder avatar Apr 02 '24 18:04 dgocoder

Hey!

Thank you for the issue illustrated with a video :smile: Looks like the ScrollView makes the grid all buggy indeed.

However, I am noticing that you might want to use the header props of the grid component. Have you tried it? :) You should be able to achieve the same layout, and I'm pretty sure it will work properly in that case!

Edit: oh, I hadn't noticed that you had a horizontal scrollable content for your categories. Maybe a scroll view inside the header might work. I haven't tried though :scream:
Maybe the horizontal virtualized list will still work as a header in the Grid, I don't know either.

pierpo avatar Apr 02 '24 19:04 pierpo

Hey @pierpo thanks for quick response. So the scrollview worked however the focus coming back is jumping back to the same number in the grid.

https://github.com/bamlab/react-tv-space-navigation/assets/13984190/ebc1011a-b451-4a49-a7df-afba5c6a5859

Using the list sort of does the inverse as you can see. Also with both scrollview and list in the on select in the header for whatever reason its jumping down in the screen. Any thoughts?

https://github.com/bamlab/react-tv-space-navigation/assets/13984190/bc359bf8-620e-4624-97b0-47df87be8d36

code below is just mainly the move to the category section/todo title into header component as you suggested.

const Header = useCallback(
    () => (
      <View style={{ width: 1920 }}>
        <Text style={{ fontSize: 20 }} className="pb-2 font-medium text-white">
          Categorias
        </Text>
        <SpatialNavigationVirtualizedList
          orientation="horizontal"
          data={categoriesIndexed}
          renderItem={renderCategory}
          itemSize={178}
          numberOfRenderedItems={WINDOW_SIZE}
          numberOfItemsVisibleOnScreen={6}
          onEndReachedThresholdItemsNumber={6}
        />
        <Text
          style={{
            fontSize: 20,
            padding: 0,
            marginTop: -5,
          }}
          className="font-medium text-white"
        >
          {categoryMapping[catFilter] ?? "Todas"}
        </Text>
      </View>
    ),
    [catFilter],
  );

  return (
    <Screen>
      {/* <DefaultFocus> */}
      <SpatialNavigationScrollView
        offsetFromStart={140}
        style={{ backgroundColor: "black" }}
      >
        <View className="h-[1000px] bg-black px-4 pt-[18.5px]">
          {/* {!moviesLoading ? ( */}
          <SpatialNavigationVirtualizedGrid
            data={schedulePrograms}
            style={{ backgroundColor: "#000000" }}
            renderItem={renderItem}
            itemHeight={207 * 1.075}
            header={<Header />}
            headerSize={180}
            numberOfColumns={NUMBER_OF_COLUMNS}
            numberOfRenderedRows={NUMBER_OF_RENDERED_ROWS}
            numberOfRowsVisibleOnScreen={NUMBER_OF_ROWS_VISIBLE_ON_SCREEN}
            onEndReachedThresholdRowsNumber={INFINITE_SCROLL_ROW_THRESHOLD}
            scrollInterval={150}
          />
          {/* ) : (
            <Loader />
          )} */}
        </View>
      </SpatialNavigationScrollView>
      {/* </DefaultFocus> */}
    </Screen>
  );
};

dgocoder avatar Apr 04 '24 05:04 dgocoder

Thank you for the very detailed answers, that's awesome :)

I just had an idea 😎 Could you wrap your header with a SpatialNavigationNode ? No props, nothing. It will probably absorb the grid alignment. I haven't checked, but there's a good chance it will work 😊

pierpo avatar Apr 04 '24 13:04 pierpo

@pierpo unfortunately adding SpatialNavigationNode did not solve it.

const Header = useCallback(
    () => (
      <SpatialNavigationNode>
        <View style={{ width: 1920 }}>
          <Text
            style={{ fontSize: 20 }}
            className="pb-2 font-medium text-white"
          >
            Categorias
          </Text>
          <SpatialNavigationVirtualizedList
            orientation="horizontal"
            data={categoriesIndexed}
            renderItem={renderCategory}
            itemSize={178}
            numberOfRenderedItems={WINDOW_SIZE}
            numberOfItemsVisibleOnScreen={6}
            onEndReachedThresholdItemsNumber={6}
          />
          <Text
            style={{
              fontSize: 20,
              padding: 0,
              marginTop: -5,
            }}
            className="font-medium text-white"
          >
            {categoryMapping[catFilter] ?? "Todas"}
          </Text>
        </View>
      </SpatialNavigationNode>
    ),
    [catFilter],
  );

  return (
    <Screen>
      <DefaultFocus>
        <SpatialNavigationScrollView
          offsetFromStart={140}
          style={{ backgroundColor: "black" }}
        >
          <View className="h-[1000px] bg-black pr-4 pt-[18.5px]">
            {moviesLoading ? (
              <Loader />
            ) : (
              <SpatialNavigationVirtualizedGrid
                data={schedulePrograms}
                style={{ backgroundColor: "#000000" }}
                renderItem={renderItem}
                itemHeight={207 * 1.075}
                header={<Header />}
                headerSize={185}
                numberOfColumns={NUMBER_OF_COLUMNS}
                numberOfRenderedRows={NUMBER_OF_RENDERED_ROWS}
                numberOfRowsVisibleOnScreen={NUMBER_OF_ROWS_VISIBLE_ON_SCREEN}
                onEndReachedThresholdRowsNumber={INFINITE_SCROLL_ROW_THRESHOLD}
                scrollInterval={150}
              />
            )}
          </View>
        </SpatialNavigationScrollView>
      </DefaultFocus>
    </Screen>
  );
};

https://github.com/bamlab/react-tv-space-navigation/assets/13984190/3ddbd657-3f7c-42ae-a114-9255759dc6d2

dgocoder avatar Apr 06 '24 05:04 dgocoder

Indeed! Thank you for all the videos, @dgocoder. That really makes it simpler.

About the problem, I think I have an idea. I need to try it out. I think the header is wrapped in the same SpatialNavigationNode with grid enabled, so it behaves like a grid, as the rest. If we split the header and the content of the grid into two different nodes, it will probably work.

One problem will remain though, for which I have no idea of a simple solution: since the grid is virtualized, at some point the row above in the header will be unmounted... And it will lose track of the focused item.

Btw, have you considered implementing a non virtualized grid? If you are guaranteed to have no more than ~100 elements, I think the grid would work decently in most cases for most devices 😄 And you'd have more control on it.

pierpo avatar Apr 09 '24 08:04 pierpo

Unfortunately I do have more than 100 elements. In regard to it losing focus, ideally if I didn't have to put the virtualized list in the header (which we did because of the original issue) then perhaps we wouldn't have to worry about it not rendering. I really don't need the horizontal list to be virtualized.

dgocoder avatar Apr 10 '24 04:04 dgocoder

Unfortunately, the problem lies in the Grid and not the virtualized list above. The header is actually... a grid element. So it inherits the grid system.

We need to change that!

pierpo avatar Apr 12 '24 17:04 pierpo

@pierpo while I know the header is a grid element, what if we didn't use header component? When I tried to do so we had a different issue with scrolling but in reality it shouldn't have to be the header. I know that was an attempt to solve the issue by using header but perhaps theres another remedy?

dgocoder avatar Apr 22 '24 23:04 dgocoder

Hey @dgocoder!

The problem with not using the header props is that since the virtualized grid has a fake CSS scroll, you might not be able to have something that looks nice. Unless you try to hide the header differently by detecting whether it is active or not (using SpatialNavigationNode's isActive child property), but that might look ugly. It's something that can be tried out though, if you manage to do a translate animation similar to the grid then it might be fine 😁

<HeaderRow />
<VirtualizedGrid />

with something that would be like this (assuming that shouldBeVisible triggers an animation of your choice)

const HeaderRow = () => {
  return <SpatialNavigationNode>{({isActive}) => <Header shouldBeVisible={!!isActive} />}</SpatialNavigationNode>
}

pierpo avatar Apr 30 '24 08:04 pierpo