React-native-tab-view: Dynamically adding routes not working

Created on 31 Aug 2018  路  5Comments  路  Source: satya164/react-native-tab-view


Current behaviour

Getting error:

Invariant Violation: Invariant Violation: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.

Check the render method of `SceneComponent`.

This error is located at:
    in SceneComponent (at SceneMap.js:16)
    in RCTView (at View.js:43)
    in AndroidViewPager (at ViewPagerAndroid.android.js:247)
    in ViewPagerAndroid (at PagerAndroid.js:154)
    in PagerAndroid (at TabView.js:59)
    in RCTView (at View.js:43)
    in RCTView (at View.js:43)
    in TabView (at UnderlineTabbar.js:76)

Expected behaviour

Should not throw error and run.

Code sample

  constructor(props) {
      super(props);

      this.state = {
        index: 0,
        routes: [],
      };
      props.categories.forEach(category => {
        this.state.routes.push({ key: category.description, title: category.name });
      });
  }

  render() {
    let scenes = {};
    this.props.categories.forEach(category => {
      if(category.assets.length > 0) {
        const FirstRoute = () => (
          <View style={[styles.container, { backgroundColor: '#ff4081' }]} />
        );
        scenes[category.description] = FirstRoute;
      }
    });
    return (
      <TabView
        style={[styles.container, this.props.style]}
        navigationState={this.state}
        renderScene={SceneMap(scenes)}
        renderTabBar={this._renderTabBar}
        onIndexChange={this._handleIndexChange}
        initialLayout={initialLayout}
      />
    );
  }

Your Environment

| software | version
| --------------------- | -------
| ios or android | android
| react-native | 0.56
| react-native-tab-view |1.0.0
| node |8.11.4
| npm or yarn |6.4.0

Most helpful comment

After further debugging into the code, this issue is now fixed for me, and is now able to create dynamic routes.

Thanks for the info @satya164.

@khat33b - please try below solution, which might help you as well:

constructor(props: Props) {
    super(props)

    this.state = {
      index: 0,
      routes: [],
    }

    props.list.forEach(tab => {
      this.state.routes.push({
        key: tab.tabTitle,
        title: tab.tabTitle,
      })
    })
  }

getSceneMap = () => {
    let sceneMap = {}

    this.props.list.map((item, index) => {
      sceneMap[item.tabTitle] = () => {
        return item.tabSceneComp // some component for tab scene
      }
    })

    return sceneMap
  }

  _renderScene = SceneMap(this.getSceneMap())

  render() {
    return (
      <TabView
        navigationState={this.state}
        renderScene={this._renderScene}
        renderTabBar={this._renderTabBar}
        onIndexChange={this._handleIndexChange}
      />
    )
  }

For me, we are trying to pass tabTitle & tabSceneComp as props.
Hope this helps.

All 5 comments

I'm also getting the same issue, and have already looked into #601 & #465.
Can somebody help?
Thanks,

Please don't create components inside render. It'll cause a remount on every update which is very bad. Move it to constructor/lifecycle methods and store them in state to avoid recreating the same component.

Here you're creating routes for all categories in constructor, but only creating components for categories that have assets.length > 0, so it's obviously going to error for routes which have assets.length === 0.

Also please don't create multiple issues for the same issue.

@skbhardwaj please create an issue with a runnable demo, preferable on https://snack.expo.io

After further debugging into the code, this issue is now fixed for me, and is now able to create dynamic routes.

Thanks for the info @satya164.

@khat33b - please try below solution, which might help you as well:

constructor(props: Props) {
    super(props)

    this.state = {
      index: 0,
      routes: [],
    }

    props.list.forEach(tab => {
      this.state.routes.push({
        key: tab.tabTitle,
        title: tab.tabTitle,
      })
    })
  }

getSceneMap = () => {
    let sceneMap = {}

    this.props.list.map((item, index) => {
      sceneMap[item.tabTitle] = () => {
        return item.tabSceneComp // some component for tab scene
      }
    })

    return sceneMap
  }

  _renderScene = SceneMap(this.getSceneMap())

  render() {
    return (
      <TabView
        navigationState={this.state}
        renderScene={this._renderScene}
        renderTabBar={this._renderTabBar}
        onIndexChange={this._handleIndexChange}
      />
    )
  }

For me, we are trying to pass tabTitle & tabSceneComp as props.
Hope this helps.

@skbhardwaj thanks. May I ask what prop you have to change(maybe due to a button press for example) inorder to push the new screen to the TabView? is it props.list in this case?

is this answer against docs? IMPORTANT: Do not pass inline functions to SceneMap

Was this page helpful?
0 / 5 - 0 ratings

Related issues

satya164 picture satya164  路  3Comments

jasonkw9 picture jasonkw9  路  3Comments

compojoom picture compojoom  路  4Comments

ashusdn picture ashusdn  路  4Comments

t3chnoboy picture t3chnoboy  路  3Comments