-
Notifications
You must be signed in to change notification settings - Fork 429
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
Dynamic / lazy approach to load pages #104
Comments
I really need this feature |
Basic js (ts) side implementation. Works well enough for my purposes. You may be able to adapt. import React, {
forwardRef,
Ref,
useEffect,
useImperativeHandle,
useRef,
useState,
} from 'react';
import {NativeSyntheticEvent, StyleProp, View, ViewStyle} from 'react-native';
import ViewPager, {
ViewPagerOnPageSelectedEventData,
} from '@react-native-community/viewpager';
type PageSelectedEvent = NativeSyntheticEvent<ViewPagerOnPageSelectedEventData>;
export type RenderItem<T> = (info: {
item: T;
itemIndex: number;
visiblePage: number;
}) => React.ReactElement;
export type LazyViewPagerHandle = {setPage(selectedPage: number): void};
interface LazyViewPagerProps<T> {
/**
* Number of items to render before and after the current page. Default 1.
*/
buffer?: number;
data: T[];
/**
* Index of starting page.
*/
initialPage?: number;
onPageSelected?: (page: number) => void;
renderItem: RenderItem<T>;
style?: StyleProp<ViewStyle>;
}
function computeOffset(page: number, numPages: number, buffer: number) {
const windowLength = 1 + 2 * buffer;
let offset: number;
if (page <= buffer || numPages <= windowLength) {
offset = 0;
} else if (page >= 1 + numPages - windowLength) {
offset = Math.max(0, numPages - windowLength);
} else {
offset = page - buffer;
}
return offset;
}
function sleep(milliseconds: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, milliseconds));
}
function renderPage<T>(
renderItem: RenderItem<T>,
item: T,
itemIndex: number,
visiblePage: number,
buffer: number,
) {
const delta = Math.abs(itemIndex - visiblePage);
return (
<View key={itemIndex}>
{delta <= buffer ? renderItem({item, itemIndex, visiblePage}) : null}
</View>
);
}
function LazyViewPagerImpl<T>(
props: LazyViewPagerProps<T>,
ref: Ref<LazyViewPagerHandle>,
) {
// Internal buffer is larger; supports paging.
const internalBuffer = 8;
const buffer =
(props.buffer == null ? 1 : Math.max(0, props.buffer)) + internalBuffer;
// When set to `true`, forces `ViewPager` to remount.
const [isRefreshing, setIsRefreshing] = useState(false);
const [page, setPage] = useState(() =>
props.initialPage == null ? 0 : Math.max(0, props.initialPage),
);
const [offset, setOffset] = useState(() =>
computeOffset(page, props.data.length, buffer),
);
const targetOffset = useRef(offset);
const vpRef = useRef<ViewPager>(null);
const onPageSelected = (event: PageSelectedEvent) => {
if (offset === targetOffset.current) {
setPage(event.nativeEvent.position + offset);
}
};
useEffect(() => {
if (isRefreshing) {
setIsRefreshing(false);
}
}, [isRefreshing, setIsRefreshing]);
useEffect(() => {
const state = {live: true};
// Rate limit offset updates.
sleep(1100).then(() => {
if (state.live) {
targetOffset.current = computeOffset(page, props.data.length, buffer);
setOffset(targetOffset.current);
}
});
return () => {
state.live = false;
};
}, [buffer, page, props.data.length, setOffset, targetOffset]);
// Broadcast page selected event.
const clientOnPageSelected = props.onPageSelected;
useEffect(() => {
if (clientOnPageSelected != null) {
clientOnPageSelected(page);
}
}, [clientOnPageSelected, page]);
const windowLength = 1 + 2 * buffer;
useImperativeHandle(
ref,
() => ({
setPage: (selectedPage: number) => {
if (vpRef.current != null) {
const vpPage = selectedPage - offset;
if (vpPage >= 0 && vpPage < windowLength) {
// Inside render window, navigate normally.
vpRef.current.setPage(vpPage);
return;
}
}
// Remount component to navigate to `selectedPage`.
// TODO: Is there a cleaner way that does not involve forcing a
// rebuild of `ViewPager`?
const newOffset = computeOffset(
selectedPage,
props.data.length,
buffer,
);
targetOffset.current = newOffset;
setOffset(newOffset);
setPage(selectedPage);
setIsRefreshing(true);
},
}),
[
buffer,
offset,
props.data.length,
setIsRefreshing,
setOffset,
setPage,
targetOffset,
vpRef,
windowLength,
],
);
return isRefreshing ? (
<View style={props.style} />
) : (
<ViewPager
initialPage={page - offset}
ref={vpRef}
style={props.style}
onPageSelected={onPageSelected}>
{props.data
.slice(offset, offset + windowLength)
.map((item, index) =>
renderPage(
props.renderItem,
item,
offset + index,
page,
buffer - internalBuffer,
),
)}
</ViewPager>
);
}
export const LazyViewPager = forwardRef(LazyViewPagerImpl); Also import React from 'react';
declare module 'react' {
// Redefine to better support generics.
function forwardRef<T, P = {}>(
render: (props: P, ref: React.Ref<T>) => React.ReactElement | null,
): (
props: React.PropsWithoutRef<P> & React.RefAttributes<T>,
) => React.ReactElement | null;
} |
Hey! |
i want lazy loading anyone has another ways? |
@alpha0010 Thanks for sharing your solution. I've verified it does work, with one caveat. When I prepend the list, it looses the position of the current page. If I make the buffer sufficiently large, this doesn't happen, but performance degrades. It also doesn't happen using vanilla react-native-pager. Wondering if you have any thoughts as to why this might be the case? I'm hoping for a way to rectify. |
Do you have any solutions? We need this function very much. Thank you very much. |
This feature has been implemented here: Any feedback will be appreciated |
I have found one improvable thing: |
Do you have any recommendation how to do so? It is already wrapped with react-native-pager-view/src/LazyPagerView.tsx Lines 243 to 254 in 306d899
|
Hmm... simply placing it as the last thing in the function doesn't help? |
Hey! quick questions if you people can please answer,
|
|
Hey Alpha,
Thank you for the reply.
Considering my every view has full width and height, I will stick to
flatlist then.
My usecase is basically each card taking up the screen and horizontal
scroll.
…On Fri, Jul 2, 2021, 1:56 AM Alpha ***@***.***> wrote:
1. Flatlist manages a consecutive sequence of views. If these views
are the same size as the screen, and scroll snapping is enabled, it will
function similarly to the viewpager. However the location is a pixel offset
from the top of the flatlist. This can most easily observed on device
rotation (will see parts of the adjacent views, and likely end up on a
different view than prior to rotate). Viewpager manages pages. Each page is
a view. Events and operations work at page level; compare to flatlist where
pixel level (which can be translated back to pages with a bit of math).
- Flatlist can work as a viewpager, but viewpager cannot work as a
flatlist. For page level operations, viewpager will be simpler and require
fewer workarounds.
2. Not quite the same: set buffer
<https://github.com/callstack/react-native-pager-view/blob/next/src/types.ts#L145>
appropriately; have pages begin loading data on mount.
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<#104 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AI36LMJSWAKUGPQ7IOSZIHDTVTFPDANCNFSM4JPRFHOQ>
.
|
Moved from flatlist to pagerview for my implementation, excellent performance when it comes to horizontal swipes even for large dynamic data. Thank you for this. |
Hey this has been as RC release for quite some time, with many regular releases after it. Are there still some issues with this or what is the reasoning not making a non-RC release with lazy pager included? I am wondering whether to try this feature out, but I would rather wait if it is still considered not release ready. Thanks! |
Hey, I experimented on my large list of webviews (and i mean, very very large list) and it has the best behaviour among other libs i used. On prod i use the version 4.2.0 and it works nice, but this one is better. I will have to stick with the old version because the vertical scroll from the webviews messes around with the scroll from the pager view. But anyway, good job! |
@troZee are you able to respond to this, feel like a lot of users would like to know and would help them when making a choice of v5 vs v6. |
I think, we can implement JS windowing effect like this https://twitter.com/Tr0zZe/status/1572897540122574849 to achieve lazy loading. Thanks to fabric, it would be much easier to implement and more powerful. Right now, we are focused on fabric migration, so we will return to this, once we finished fabric migration. This lazy approach will be only available for new arch. cc @krozniata |
So for those of us who cannot transition to fabric, will the 6.0.0 RCs work, or do we need to try and use FlatList instead? EDIT: I guess my real question is, what is the most recent/most stable version/RC of lazy paging that will work with the old architecture? |
And just for completeness sake the reason I and many others cannot transition to fabric/new architecture is the various incompatible combinations of Hermes, Expo, |
I use |
Feature Request
Why it is needed
Performance way to load a LOT of pages dynamically to create a truly dynamic swiper.
Possible implementation / Code sample
Right now I can do something like:
Contraints: List keeps growing while you swipe.
A better way instead of rendering
null
would be to slice from the beginning too:However, this approach has a problem. Consider the scenario:
1 2 3 4
and position = 2 (selected element is 3).We slice from the beginning and we render:
2 3 4 5
but still position = 2 (selected element will be 4). <-- The problem is that if we change the children in this way, we need to adapt the position (here the position should be 1 for selected element to be still 3).Another approach would be doing this by default natively: #83.
The text was updated successfully, but these errors were encountered: