ptomasroos/react-native-scrollable-tab-view

Consider renaming `initialPage`

grabbou opened this issue ยท 22 comments

It's actually currentPage since it reacts to changes https://github.com/brentvatne/react-native-scrollable-tab-view/blob/master/index.js#L55 (or maybe at least clarify that in docs?)

absolutely agree

It depends on expected by user behaviour, we can either:

  • revert old initialPage behaviour & implement page prop
  • clarify that in docs, make page prop and log that initialPage is deprecated and will be removed with next version

First option is better for me.

related issues #137 , #135 ,#128 ,#129

So what was the conclusion here - how can this problem of popping back to the initial set page be solved?

@robtg4 i think it's not the component job to do so it's the user implementation to decide the initial state using redux or what ever he wants

eseQ commented

UPDATE 2 - solution
I returned to this problem again and found simple solution.
For control with Redux we don`t need use page - we need goToPage (how could i miss it before?)

UPDATE

All of i wrote next will work only for controlled tabs with Redux, but i catch scroll back if just swipe to other tab (if change with Redux or tap on TabBar all work fine).
But for control with Redux next solution work well.


TL;DR
Add identical initialPage and page on start, don`t change initialPage, change only page;
Update don`t use page - only initialPage on start and goToPage for change page.

I use redux for save my current tab (I change it in other place).
I want change active tab in drawer if that route active or initial specific tab if go from other route.

constructor() {
  super();
  this.pageProps = {} // can declare just in class
  this.pageIndex = null // ** Update **
}

Ok we have object for save component`s page data;
!important; keep it in mind! For initial Tabs we need set identical initialPage and page (use goToPage, if use page u can catch slide back). But for change tab we don`t need change initialPage. If we change initialPage without unmount component or set different values - tabs jump out of range and tab will not be loaded -_-

handleChangeScreen = ({ i }) => {
  this.pageIndex = i; // **Update ** save current page index
  this.props.dispatch(setMainScreen(i));
}

just handle onChangeTab and set in Redux (mainScreen - name of var for current tab in Redux)
Next step:

if (!this.pageProps.initialPage) this.pageProps.initialPage = mainScreen;

mainScreen - index current tab. Don`t change initialPage if tabs already init.


if (!this.pageProps.page || !this.pageProps.page !== mainScreen) this.pageProps.page = mainScreen;

Set page for init or change if page changed in Redux. Don`t use this code

Update
Now we need catch receive new mainScreen if it change outside

componentWillReceiveProps(nextProps) {
  const { YouStore: { mainScreen } } = nextProps;
  if ( this.props.YouStore.mainScreen !== mainScreen && mainScreen !== this.pageIndex) {
    if (this.tabView) this.tabView.goToPage(mainScreen)
  }
}

<ScrollableTabView
  {...this.pageProps}
  onChangeTab={this.handleChangeScreen}
  ref={(tabView) => { this.tabView = tabView; }} // set tabView
  ...
/>

Add props to ScrollableTabView. Have fun.

I try explain in detail, perhaps i can`t did it =).

i think many components has an issue with interacting with redux the main reason is sometime you need redux to control the component and sometime you need the component to control redux maybe all components should consider doing the following scenario:

  • from user interaction (e.g click, slide)
    ==run==> internal event handler (this.onClick)
  • from internal handler
    ==if external handler exits ==> run (this.props.onClick)
  • after external handler call
    == if return true ==> change internal state (this.setState({page}))
    == if return false ==> do nothing
  • listen for props (e.g componentWillReceiveProps)
    == if prop not equal state ==> change internal state (this.setState({page:this.props.page}))

in this way you can listen to the event cancel it and change your redux state in the other hand redux will change your component prop again :)

eseQ commented

@digital-flowers yeah, i agree, that will be awesome. If we don`t control component - just do u job, but if we do - doesn`t disturb =)

exactly !

eseQ commented

UPDATE
I returned to this problem again and found simple solution.
For control with Redux we don`t need use page - we need goToPage (how could i miss it before?)
I updated my past solution.

Sorry for long read

@dictory Could you elaborate how you would use goToPage in a redux setup? I can't really figure out what you mean is your solution in the previous comment with all those updates :)

eseQ commented

@Froelund, my post with update actual now(doesn't content old code).
That work for me on iOS. Have no idea about android - test it )
Save actual page index (or name if you want) to Redux store. Two point:

  1. In component with tabs:
handleChangeScreen = () => {
  this.pageIndex = i; // save local too
  this.props.dispatch(saveCurrentTabToRedux(i));
}
...
<ScrollableTabView
  ...
  onChangeTab={this.handleChangeScreen}
>
  1. Change page outside (for example):
this.props.dispatch(saveCurrentTabToRedux(2));

Next. Add ref to ScrollableTabView

<ScrollableTabView
  ...
  ref={(tabView) => { this.tabView = tabView; }}
>

Detect page change outside:

componentWillReceiveProps(nextProps) {
  const { YouStore: { pageIndex } } = nextProps;
  if ( this.props.YouStore.mainScreen !== pageIndex && pageIndex !== this.pageIndex) {
    if (this.tabView) this.tabView.goToPage(pageIndex)
  }
}

You are awesome. Don`t forget set initialPage.

@dictory :
Hi, I did as you say.
But I realized that by using, onChangeTab, I undergo some slowdowns, then the parameter that returns the function is an Obj not an int.

@grabbou , @digital-flowers : So I ask is there a way to know index of the tab that is selected in every moment, how can I do, I do not know using its reference by chance?

fqr1 commented

@dictory
It worked like a charm

Just one thing, in this section:

handleChangeScreen = () => { this.pageIndex = i; // save local too this.props.dispatch(saveCurrentTabToRedux(i)); }

the variable "i" is not mentioned where it comes from, should be

handleChangeScreen = ({i}) => { this.pageIndex = i; // save local too this.props.dispatch(saveCurrentTabToRedux(i)); }

in @dictory example instead of using the local variable this.pageIndex you can just use this.tabView.state.currentPage

@dictory I am finding it hard to understand your instructions. Can you please provide an Expo snack for this?

eseQ commented

@khat33b sorry, I stoped use this library in my projects.

@eseQ , what library do you use now? could you share with me? thanks

eseQ commented

@newCaoTao, @khat33b last year I work on non react-native project.
But I find this:

class MainScreen extends React.Component {
  tabView = null;
  loadCount = 0;
  screenKey = null;
  initialPage = null;
  screens = [
    {
      key      : 'tags',
      component: () => <TagsListAll key="tags" tabLabel="#" />,
    },
    ...
  ]
  componentWillReceiveProps(nextProps) {
    // mainScreen from store
    const { mainScreen } = nextProps;
    if (
      this.props.mainScreen !== mainScreen && mainScreen !== this.screenKey &&
      this.loadCount > 2 && this.screenKey !== null && !!this.tabView
    ) this.tabView.goToPage(this.getScreenIndex(nextProps));
  }
  getScreenIndex = (props = this.props) => findIndex(this.screens, ['key', props.mainScreen]);
  renderTabBar = () => (
    <ScrollableTabBar
      style={styles.tabBar}
      tabsContainerStyle={styles.tabsContainer}
      tabStyle={styles.tab}
    />
  )
  handleChangeScreen = ({ i }) => {
    this.screenKey = (this.screens[i] && this.screens[i].key) || (this.otherPosts[i] && this.otherPosts[i].key);
    this.props.dispatch(setMainScreen(this.screenKey));
  }
  getList = list => map(list, screen => screen.component({ settings: this.props.Settings }))
  render() {
    const { mainScreen } = this.props;
    const screens = this.getList(this.screens.concat(this.otherPosts));
    this.loadCount += 1; // hook strange bug
    if (this.initialPage === null) {
      const screenIndex = this.getScreenIndex();
      this.screenKey = mainScreen;
      this.initialPage = screenIndex;
    }
    return (
      <ScrollableTabView
        initialPage={this.initialPage}
        onChangeTab={this.handleChangeScreen}
        renderTabBar={this.renderTabBar}
        ref={(tabView) => { this.tabView = tabView; }}
        ...
      >
        {screens}
      </ScrollableTabView>
    );
  }
}

export default MainScreen;

I recommend use functional component and useEffect instead componentWillReceiveProps.

componentWillReceiveProps

having trouble with the StateLess component.
when the onChangeTab trigger. it re-redner the component twice .
not sure why