EDIT: reproduced with an expo snack that appears to be related to standard usage, more so than my particular usage: https://snack.expo.io/BJ2CHQRVm
ORIGINAL:
Currently working with a nested recyclerlistview, similar to this project. The difference is that the horizontal and vertical recyclerlistsviews are paginated, meaning their data sources must be updated periodically.
The way I'm currently doing this is to construct the dataproviders and contextproviders for each child row in the parent component. The issue is that the context provider doesn't seem to pass down correctly in props. When recycling large lists, the context provider reuses the context provider from other uniqueIds. This results in a very unfun scrolling experience - as horizontal scroll positions are not preserved when the component is recycled.
Parent dataprovider:
this.dataProvider = new DataProvider((r1, r2) => {
return r1.userId !== r2.userId;
});
Constructing object for each row for the parent dataProvider:
getDataForRow = uniqueId => {
let contextProvider;
if (this._childrenContextProviders[uniqueId]) {
contextProvider = this._childrenContextProviders[uniqueId];
} else {
this._childrenContextProviders[uniqueId] = new ContextHelper(uniqueId);
contextProvider = this._childrenContextProviders[uniqueId];
}
let dataProvider;
if (this._childrenDataProviders[uniqueId]) {
dataProvider = this._childrenDataProviders[uniqueId];
} else {
this._childrenDataProviders[uniqueId] = new DataProvider((r1, r2) => {
return r1 !== r2;
});
dataProvider = this._childrenDataProviders[uniqueId];
}
return {
uniqueId: uniqueId,
contextProvider,
dataProvider,
};
};
Then my renderRow() function looks as follows:
_rowRenderer = (type, data) => {
if (data.userId === 'header') {
return this.renderListHeader();
}
return (
<CellComponent
uniqueId={data.uniqueId}
contextProvider={data.contextProvider}
dataProvider={data.dataProvider}
/>
);
};
here is an expo snack that reproduces the issue (at least on iOS), using the same code from the context provider example project but with 100 children rows in order to force the recycling: https://snack.expo.io/BJ2CHQRVm
To reproduce, scroll one row to the right. Now scroll through the list quickly. Notice how rows which you haven't scrolled yet, are recycled to use the wrong context provider.
ContextProvider only works while mounting. During recycling nothing will mount again. If you want to recycle RecyclerListView instances you'll have to cache scroll positions manually and apply them again in componentDidUpdate inside your component. Let me know if this helps.
Hey, that makes sense with regard to the context provider. Decided to go ahead on cache in componentDidUpdate and then use scrollTo to update the list. Still running into some bugs though. While the cacheing mechanism works fine (getting the right location). The scrollTo() method tends to just not run.
e.g. standard console output:
scrolled from 341 to 341 supposed to scroll to 0
here's an expo snack reproducing (scroll 3 or 4 rows in the top to various spots, then scroll through the list and note the blank spaces).
https://snack.expo.io/S1VcpukH7
Any ideas? Do refs get messed up in the recycle process?
Perhaps it's something with _pendingScrollToOffset? Does the recycle process set that - and then void any other scrollTos?
It appears something like that is the issue...delaying the scrollTo 100ms solves the issue, but feels like an ugly hack.
setTimeout(() => {
this.list.scrollToOffset(
this.props.getCachePosition(this.props.userId),
0,
false,
);
}, 100);
It is working fine @alexpareto. Problem is with your log statement
this.list.scrollToOffset(
this.props.getCachePosition(this.props.uniqueId),
0,
false
);
console.log(
'scrolled from ',
this.props.getCachePosition(prevProps.uniqueId),
'to',
this.list.getCurrentScrollOffset(),
'supposed to scroll to',
this.props.getCachePosition(this.props.uniqueId)
);
Given scrollToOffset is asynchronous this.list.getCurrentScrollOffset() wouldn't have updated by the time you are logging it. Same is true with DataProvider change.
If I wrap the log part in double rAF it works fine. Check: https://snack.expo.io/B1B0YU-HX
Ah yes, you're right! good catch. Thanks for the help + the component.
Hello i wanted to ask something , can i use the ContextProvider in the web as i tried many times and it didn't work it seems i am missing something !
@naqvitalha I know this is a fairly old issue, but I'm still having problems with scrollToOffset.
Using https://snack.expo.io/B1B0YU-HX it works in all situations except when one of the horizontal lists is manually scrolled to the very end. In that situation, the scrollToOffset function does not fire at all and the new lists using the recycled component are scrolled to the end as well.
Interestingly, this is device-dependent, and adding extra print statements at the top causes the issue to go away. It seems to be very dependent on timing - some devices/simulators have the issue, others don't.
As a workaround, going back to the hack that @alexpareto used with an explicit timeout fixes the issue entirely.
Most helpful comment
It is working fine @alexpareto. Problem is with your log statement
Given
scrollToOffsetis asynchronousthis.list.getCurrentScrollOffset()wouldn't have updated by the time you are logging it. Same is true withDataProviderchange.If I wrap the log part in double rAF it works fine. Check: https://snack.expo.io/B1B0YU-HX