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

Possible stableId collision when scrolling too fast #730

Closed
2 tasks done
madmeatballs opened this issue Jan 12, 2023 · 14 comments
Closed
2 tasks done

Possible stableId collision when scrolling too fast #730

madmeatballs opened this issue Jan 12, 2023 · 14 comments
Labels
bug Something isn't working needs-reproduction

Comments

@madmeatballs
Copy link

madmeatballs commented Jan 12, 2023

Current behavior

Using MasonryFlashList when scrolling too fast it results to "Possible stableId collision" then leaves a large blank space until it continues to show my product cards. (Data comes from an API, which has offset pagination, onEndReached it is called to update the list.)

Expected behavior

no "Possible stableId collision", list should render accordingly without any issues.

To Reproduce

Scroll too fast.

Platform:

  • iOS
  • Android

Environment

"@shopify/flash-list": "^1.4.0",
"react-native": "0.68.5",

@madmeatballs madmeatballs added the bug Something isn't working label Jan 12, 2023
@hirbod
Copy link
Contributor

hirbod commented Jan 13, 2023

Usually this issue happens only if you really have duplicate content. Are you using a key extractor? If yes, try to use the index or omit it when you are not animating. In my cases, keyextractor never changed anything significant performance wise. If that issue still persists, the issue might come through pagination. I had a case, where the data in my database changed while I was paginating, so items, which had be on page 1, shifted and ended up being on the second page, causing an exact duplicate. The best fix for scenarios is a date limit field or just a data transformer which removes duplicates.

@fortmarek
Copy link
Contributor

As @hirbod mentioned, it's unlikely that the stableId collision occurs due to the fast scrolling. You can try to reproduce this in our fixture app and we can take a look 🙂

@madmeatballs
Copy link
Author

Will checkout my data, thanks for the feedback @hirbod @fortmarek

@ngdbao
Copy link

ngdbao commented Jul 31, 2023

i'm experiencing this issue too.
For my case, Flashlist always have 1 init call (skip 0 take 10) and 1 pre-load (*) loadmore call (skip 10 take 10) at the first time load even not scrolling at all, regardless of setting onEndReachedThreshold or not

then, If i scroll down fast enough, it will trigger 1 loadmore call with exactly same (skip 10 take 10) pre-load call, which causing "Possible stableId collision"

So problem in my case is (*) pre-load loadmore call where onEndReachedThreshold doesnt work as expected at any value or null

                <AppView flex backgroundColor={PALETTE_COLOR.light}>
                    <FlashList
                        estimatedItemSize={70}
                        showsVerticalScrollIndicator={false}
                        numColumns={1}
                        onScroll={onScroll}
                        data={dataFeaturesTrips || []}
                        renderItem={renderItem}
                        keyExtractor={keyExtractor}
                        onMomentumScrollBegin={onMomentumScrollBegin}
                        onEndReached={onEndReached}
                        onEndReachedThreshold={0.1}
                        refreshControl={
                            <RefreshControl
                                refreshing={refreshing}
                                onRefresh={onRefresh}
                                tintColor={PALETTE_COLOR.primary}
                                colors={[
                                    PALETTE_COLOR.orange[300],
                                    PALETTE_COLOR.orange[500],
                                    PALETTE_COLOR.orange[700],
                                ]}
                            />
                        }
                        ListEmptyComponent={
                            <AppView alignCenter>
                                <AppText
                                    style={[
                                        STYLE_GLOBAL.body1,
                                        STYLE_GLOBAL.weight600,
                                        STYLE_GLOBAL.color_textcontent,
                                    ]}>
                                    Waiting...
                                </AppText>
                            </AppView>
                        }
                        ListHeaderComponent={
                            <SafeAreaView
                                edges={['top']}
                                style={[styles.scrollView]}>
                                <HeaderSection
                                    marginBottom={4}
                                    hiddenCategories
                                    oneLine
                                    isFromHome={false}
                                />
                            </SafeAreaView>
                        }
                        ListFooterComponent={
                            <AppView paddingBottom={getSize.m(48)} />
                        }
                    />
                </AppView>

if i change "FlashList" to "FlatList", problem gone.

@ngdbao
Copy link

ngdbao commented Jul 31, 2023

i'm experiencing this issue too. For my case, Flashlist always have 1 init call (skip 0 take 10) and 1 pre-load (*) loadmore call (skip 10 take 10) at the first time load even not scrolling at all, regardless of setting onEndReachedThreshold or not

turn out it's about estimatedItemSize too small, 300 is fitting for me now

@efstathiosntonas
Copy link
Contributor

efstathiosntonas commented Sep 29, 2023

What I did and I've never encountered the stableId collision again is to add the index to the keyExtractor like so:

 const keyExtractor = useCallback((item: any, i: number) => `${i}-${item.id}`, []);

I was constantly having issues with it since my list is 100% real-time but now the issues are gone. Can't explain why adding the index fixes it, it seems it works automagically now 🤷

@chj-damon
Copy link

What I did and I've never encountered the stableId collision is to add the index to the keyExtractor like so:

 const keyExtractor = useCallback((item: any, i: number) => `${i}-${item.id}`, []);

I was constantly having issues with it since my list is 100% real-time but now the issues are gone. Can't explain why adding the index fixes it, it seems it works automagically now 🤷

you saved my day!

@hlspablo
Copy link

hlspablo commented Apr 3, 2024

What I did and I've never encountered the stableId collision again is to add the index to the keyExtractor like so:

 const keyExtractor = useCallback((item: any, i: number) => `${i}-${item.id}`, []);

I was constantly having issues with it since my list is 100% real-time but now the issues are gone. Can't explain why adding the index fixes it, it seems it works automagically now 🤷

just as additional info, this code prevents key duplication at all, since it combines the id and index, but the warning should not be blindly ignored since it can be a sign of a bug in the pagination.

@MichalKrakow
Copy link

MichalKrakow commented Apr 10, 2024

How about basic array splice, same story:

comments.splice(index, 1);
setComments([...comments]);

produces "Possible stableId collision" even though the index in question is clearly gone.
Index extractor relyes on comment.id so it has zero chance of reocurring after delete.

I mean... it is a warning rather then error and its says "possible". After multiple tests it didnt produce any unwanted behaviour but im left with impression im doing something wrong.

Someone faced the same thing?

@hlspablo
Copy link

How about basic array splice, same story:

comments.splice(index, 1);
setComments([...comments]);

produces "Possible stableId collision" even though the index in question is clearly gone. Index extractor relyes on comment.id so it has zero chance of reocurring after delete.

I mean... it is a warning rather then error and its says "possible". After multiple tests it didnt produce any unwanted behaviour but im left with impression im doing something wrong.

Someone faced the same thing?

its very likely that you are doing something "wrong", what you mean with clearly gone?!

@MichalKrakow
Copy link

Ok some basic JS magic behind the scenes that probably i should be aware of.

const newList = [...comments];
newList.splice(index, 1);
setComments(newList);

this does not raise warnings upon item removal.
Copying the list priror to modification solved the issue.

hyochan added a commit to crossplatformkorea/ExpoBriefing that referenced this issue Aug 9, 2024
@hyochan
Copy link

hyochan commented Aug 9, 2024

What I did and I've never encountered the stableId collision is to add the index to the keyExtractor like so:

 const keyExtractor = useCallback((item: any, i: number) => `${i}-${item.id}`, []);

I was constantly having issues with it since my list is 100% real-time but now the issues are gone. Can't explain why adding the index fixes it, it seems it works automagically now 🤷

you saved my day!

I think if you don't use useCallback, the keyExtractor function gets recreated during each re-render in React. This can cause the keys returned by keyExtractor to be inconsistent. Specifically, when this function is frequently regenerated, there’s a possibility that the same item might receive different keys between previous and current renders. As a result, the consistency of keys isn't maintained, leading React to potentially misidentify the same item as different ones. This inconsistency can trigger the "stableId collision" warning.

hyochan added a commit to crossplatformkorea/ExpoBriefing that referenced this issue Aug 10, 2024
@itsramiel
Copy link

What I did and I've never encountered the stableId collision again is to add the index to the keyExtractor like so:

const keyExtractor = useCallback((item: any, i: number) => ${i}-${item.id}, []);
I was constantly having issues with it since my list is 100% real-time but now the issues are gone. Can't explain why adding the index fixes it, it seems it works automagically now 🤷

You are creating a different key based on the position. So if the position changes for the same item, you are providing a different key which counters the idea of recycling according to the key. Of course this will work, but according to recycling, this is not ideal and them item will not be recycled when its position changes.

@spyshower
Copy link

What I did and I've never encountered the stableId collision again is to add the index to the keyExtractor like so:

const keyExtractor = useCallback((item: any, i: number) => ${i}-${item.id}, []);

I was constantly having issues with it since my list is 100% real-time but now the issues are gone. Can't explain why adding the index fixes it, it seems it works automagically now 🤷

a re stathi paixtara, eisai thrilos

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

No branches or pull requests