Monday, 28 January 2019

React Native: Rendering infinite number of pages (bi-directional swiping)

Question

How do people implement an infinite number of pages for you to swipe through dynamically?

Details

If an app renders a page for a day you can expect to swipe to the right for the previous day and to the left for the next day.

For example, the FitNotes app does this seemlessly: FitNotes app day view

Swiping to the right/left is the same as pressing on button 5/6. and displays already rendered pages. At the same time new pages for the now adjacent days are rendered as well.

Initially, I thought that this is something you would do using a carousel e.g., using the react-native-snap-carousel library. I imagined something like the example below but rendering a page using state variables instead of an array of images.

Example carousel from the react-native-snap-carousel library

So, if I wanted to render a page with the date at the top, I would have an array as part of the state, for example:

carouselElements: [ '2019/01/21', '2019/01/22', '2019/01/23']

Fed into the carousel below, I would get back three pages each one with the date rendered at the top.

<Carousel
    ref={(c) => { this._carousel = c; }}
    data={this.props.carouselElements}
    renderItem={this.renderDay}
    sliderWidth={sliderWidth}
    itemWidth={sliderWidth}
    enableSnap 
    firstItem={1}
    initialNumToRender={3}
    maxToRenderPerBatch={3}
    onBeforeSnapToItem={(index) => this.changeEvent(index)}
    useScrollView/>

Problem 1: Infinitely growing array

You can start at Day X and render the adjacent two days ahead of time. If I swipe to the left, you access Day X+1 and concat Day X+2 to the end of the array and render that day as well.

The array becomes: [ 'Day X-1', 'Day X', 'Day X+1', 'Day X+2']

For performance, I can keep checking the index against a 3 day window and only render the selected day and the adjacent two days. But the array will grow infinitely and your code is O(n) at best.

Problem 2: Indexing

a) Prepending elements:

You can start at Day X and and then swipe to the right. You are now showing Day X-1. You add Day X-2 to the start of the array and render it to prepare for a second swipe to the right. The problem is you added an element at index 0. Your system is pointing at the state of index 0 when it changed from Day X-1 to Day X-2, thus the page now shows Day X-2 instead.

i.e., instead of moving back one day when you swiped to the right, you moved back 2 days with each swipe.

b) Removing elements

If I remove an element to keep the array small, everything shifts but the index remains the same. The index now points to a different element than the intended one in the array and this reflects in the rendered page before the index can be corrected.

Thus my question: How do people implement dynamic page rendering ad infinitum with page swiping? It seems like a lot of apps have it and I can't find anyone talking about it.


Edits

EDIT 1: Added images with accompanying edits to explain my problem better.

EDIT 2: I have a working implementation with react-native-swiper that basically takes a massive array and only renders the batch of slides immediately adjacent to the displayed page.

<Swiper style={styles.wrapper} 
          showsButtons={false}
          index={currentDateIndex}
          loadMinimal={true}
          loadMinimalSize={1}
          showsPagination={false}
          loop={false}
          onIndexChanged={(index) => dispatch(changeSelectedDayByIndex(index))}
         >
          {dates.map((item) => 
              this.renderDay(item)
            )
          }
</Swiper>

However, the dates.map command imposes a large performance penalty at startup.

Edit 3: Using a slice command on the dates array before mapping doesn't seem like an option because I'd be rendering during an existing state transition..



from React Native: Rendering infinite number of pages (bi-directional swiping)

No comments:

Post a Comment