trad
trad

Reputation: 7731

react native ViewPagerAndroid using dynamic page views

I am implementing ViewPagerAndroid in one of my components. the child page views for the pager are dynamic (maybe that's the problem?) I get the android error 'the specified child already has a parent. you must call removeView() on the child's parent first'. here is the component:

var PageContent = React.createClass({
    propTypes: {
        page: React.PropTypes.oneOfType([
          React.PropTypes.object, React.PropTypes.bool
        ]),
        pageNext: React.PropTypes.oneOfType([
          React.PropTypes.object, React.PropTypes.bool
        ]),
        pagePrevious: React.PropTypes.oneOfType([
          React.PropTypes.object, React.PropTypes.bool
        ]),
        positionY: React.PropTypes.object,
        positionYNext: React.PropTypes.object,
        positionYPrevious: React.PropTypes.object,
        pageDensity: React.PropTypes.number,
        updatePage: React.PropTypes.func
      },
      initialPageIndex() {
        if (this.props.page.number === 1)
          return 0;
        return 1;
      },
      onPageSelected(e) {
        var initialPageIndex = this.initialPageIndex();
        var position = e.nativeEvent.position;
        console.warn(position);
        // The initial page can have an index in the PageViewer
        // of zero or one, so we must determine the new page number
        // by checking the viewer position in terms of the true pages array.
        if (position < initialPageIndex) {
          // the user turned to the previous page!
          this.props.updatePage(this.props.page.number - 1);
        } else if (position > initialPageIndex) {
          // the user turned to the next page!
          this.props.updatePage(this.props.page.number + 1);
        }
      },
      render() {
        var pagePrevious = this.props.pagePrevious;
        var page = this.props.page;
        var pageNext = this.props.pageNext;
        var pageViews = [pagePrevious, page, pageNext].map((current, i) => {
          if (!current) return null;
          var titles = <Titles page={current}/>;
          var image = current.image
            ? <Image
                source={current.image}
              />
            : null;
          var text = <PageText page={current} pageDensity={this.props.pageDensity}/>

          var view =
            <View key={i}>
              {titles}
              {image}
              {text}
            </View>;
          return view;
        }).filter((current) => {return !!current});

        // we must dynamically determine the initial page index.  
        // Do not use state because we can derive this from the current page state.
        var initialPageIndex = this.initialPageIndex();

        return (
          <View style={styles.containerViewPager}>
            <ViewPagerAndroid
              style={styles.viewPager}
              initialPage={initialPageIndex}
              pageMargin={10}
              onPageSelected={this.onPageSelected}>
              {pageViews}
            </ViewPagerAndroid>
          </View>
        );
      }
    });

I am setting the key property for each dynamic page view. Does the page viewer supports dynamic pages?

Upvotes: 2

Views: 4205

Answers (1)

trad
trad

Reputation: 7731

There were many issues with the way I was using the ViewPagerAndroid component. I was trying to accomplish lazy loading of the page view data (only load a page's data if it is adjacent to the current page), and my idea was to restrict the number of page views at any given time to those needed.

But ViewPagerAndroid behaves strangely when you begin swapping different child view components. it is perfectly fine to add an additional page view dynamically, but when you start passing new key attributes in the page views it breaks. This makes sense. the pager is designed to handle user interaction and the page changes on its own, and you can hook in to do any work you need to do. Conceptually I was trying to build an entirely new pager on each state change.

The solution is to let the page view worry about lazy loading. give the pager the views that it will need in your app, and let those child views manage their content. here is an example where the child page views aren't mutating, and each one is lazy loading (based on the user 's position in the pager):

var TestPagerAndroid = React.createClass({
  getInitialState() {
    return {
      currentPage: 0
    };
  },
  isAdjacentPage(pageNumber) {
    var currentPage = this.state.currentPage;
    var adjacentPages = [currentPage - 1, currentPage, currentPage + 1];
    return pageNumber >= 0 && pageNumber <= BOOK_LENGTH
      && adjacentPages.indexOf(pageNumber) !== -1;
  },
  getPageContent(pageNumber) {
    console.warn('store access count');
    var store = MOCK_DATA_STORE;
    return <Text>{store[pageNumber]}</Text>;
  },
  onPageSelected(e) {
    var currentPage = e.nativeEvent.position;
    this.setState({ currentPage });
  },
  render() {
    var pageViews = [];
    for (let i=0; i <=  BOOK_LENGTH; i++) {
      pageViews.push(
        <View style={styles.container} key={i}>
          <TestPageView
            pageNumber={i}
            isAdjacentPage={this.isAdjacentPage}
            getPageContent={this.getPageContent}
          />
        </View>
      );
    }
return (
      <View style={styles.containerViewPager}>
        <ViewPagerAndroid
          style={styles.viewPager}
          initialPage={0}
          pageMargin={10}
          onPageSelected={this.onPageSelected}>
          {pageViews}
        </ViewPagerAndroid>
      </View>
    );
  }
});

var TestPageView = React.createClass({
  render() {
    var pageNumber = this.props.pageNumber;
    var isAdjacent = this.props.isAdjacentPage(this.props.pageNumber);
    var content = isAdjacent
      ? this.props.getPageContent(pageNumber): null;
    return (
      <View>
        {content}
      </View>
    );
  }
});

Upvotes: 2

Related Questions